All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.athenz.common.server.store.impl.JDBCConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The Athenz Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.yahoo.athenz.common.server.store.impl;

import com.yahoo.athenz.auth.AuthorityConsts;
import com.yahoo.athenz.common.server.store.PrincipalGroup;
import com.yahoo.athenz.common.server.store.PrincipalRole;
import com.yahoo.athenz.common.server.util.ResourceUtils;
import com.yahoo.athenz.zms.*;
import com.yahoo.athenz.common.server.ServerResourceException;
import com.yahoo.athenz.common.server.store.AthenzDomain;
import com.yahoo.athenz.common.server.store.ObjectStoreConnection;
import com.yahoo.athenz.common.server.util.ResourceOwnership;
import com.yahoo.athenz.common.server.util.Utils;
import com.yahoo.rdl.JSON;
import com.yahoo.rdl.Struct;
import com.yahoo.rdl.Timestamp;
import com.yahoo.rdl.UUID;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class JDBCConnection implements ObjectStoreConnection {

    private static final Logger LOG = LoggerFactory.getLogger(JDBCConnection.class);

    private static final int MYSQL_ER_OPTION_PREVENTS_STATEMENT = 1290;
    private static final int MYSQL_ER_OPTION_DUPLICATE_ENTRY = 1062;
    private static final int MYSQL_ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101;

    private static final String MYSQL_EXC_STATE_DEADLOCK   = "40001";
    private static final String MYSQL_EXC_STATE_COMM_ERROR = "08S01";

    private static final String AUDIT_OPERATION_APPROVE = "APPROVE";
    private static final String AUDIT_OPERATION_ADD     = "ADD";
    private static final String AUDIT_OPERATION_UPDATE  = "UPDATE";
    private static final String AUDIT_OPERATION_REQUEST = "REQUEST";

    private static final String SQL_TABLE_DOMAIN = "domain";
    private static final String SQL_TABLE_ROLE = "role";
    private static final String SQL_TABLE_ROLE_MEMBER = "role_member";
    private static final String SQL_TABLE_POLICY = "policy";
    private static final String SQL_TABLE_ASSERTION = "assertion";
    private static final String SQL_TABLE_PRINCIPAL_GROUP = "principal_group";
    private static final String SQL_TABLE_PRINCIPAL_GROUP_MEMBER = "principal_group_member";
    private static final String SQL_TABLE_SERVICE = "service";
    private static final String SQL_TABLE_PUBLIC_KEY = "public_key";
    private static final String SQL_TABLE_SERVICE_HOST = "service_host";
    private static final String SQL_TABLE_ENTITY = "entity";

    private static final String SQL_DELETE_DOMAIN = "DELETE FROM domain WHERE name=?;";
    private static final String SQL_GET_DOMAIN = "SELECT * FROM domain WHERE name=?;";
    private static final String SQL_GET_DOMAIN_ID = "SELECT domain_id FROM domain WHERE name=?;";
    private static final String SQL_GET_ACTIVE_DOMAIN_ID = "SELECT domain_id FROM domain WHERE name=? AND enabled=true;";
    @SuppressWarnings("SqlResolve")
    private static final String SQL_GET_DOMAINS_WITH_NAME = "SELECT name FROM domain WHERE name LIKE ?;";
    private static final String SQL_GET_DOMAIN_WITH_AWS_ACCOUNT = "SELECT name FROM domain WHERE account=?;";
    private static final String SQL_GET_DOMAIN_WITH_AZURE_SUBSCRIPTION = "SELECT name FROM domain WHERE azure_subscription=?;";
    private static final String SQL_GET_DOMAIN_WITH_GCP_PROJECT = "SELECT name FROM domain WHERE gcp_project=?;";
    private static final String SQL_LIST_DOMAINS_WITH_AWS_ACCOUNT = "SELECT name, account FROM domain WHERE account!='';";
    private static final String SQL_LIST_DOMAINS_WITH_AZURE_SUBSCRIPTION = "SELECT name, azure_subscription FROM domain WHERE azure_subscription!='';";
    private static final String SQL_LIST_DOMAINS_WITH_GCP_PROJECT = "SELECT name, gcp_project FROM domain WHERE gcp_project!='';";
    private static final String SQL_GET_DOMAIN_WITH_YPM_ID = "SELECT name FROM domain WHERE ypm_id=?;";
    private static final String SQL_GET_DOMAIN_WITH_PRODUCT_ID = "SELECT name FROM domain WHERE product_id=?;";
    private static final String SQL_LIST_DOMAIN_WITH_BUSINESS_SERVICE = "SELECT name FROM domain WHERE business_service=?;";
    private static final String SQL_INSERT_DOMAIN = "INSERT INTO domain "
            + "(name, description, org, uuid, enabled, audit_enabled, account, ypm_id, application_id, cert_dns_domain,"
            + " member_expiry_days, token_expiry_mins, service_cert_expiry_mins, role_cert_expiry_mins, sign_algorithm,"
            + " service_expiry_days, user_authority_filter, group_expiry_days, azure_subscription, business_service,"
            + " member_purge_expiry_days, gcp_project, gcp_project_number, product_id, feature_flags, environment,"
            + " azure_tenant, azure_client, x509_cert_signer_keyid, ssh_cert_signer_keyid, slack_channel) "
            + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);";
    private static final String SQL_UPDATE_DOMAIN = "UPDATE domain "
            + "SET description=?, org=?, uuid=?, enabled=?, audit_enabled=?, account=?, ypm_id=?, application_id=?,"
            + " cert_dns_domain=?, member_expiry_days=?, token_expiry_mins=?, service_cert_expiry_mins=?,"
            + " role_cert_expiry_mins=?, sign_algorithm=?, service_expiry_days=?, user_authority_filter=?,"
            + " group_expiry_days=?, azure_subscription=?, business_service=?, member_purge_expiry_days=?,"
            + " gcp_project=?, gcp_project_number=?, product_id=?, feature_flags=?, environment=?,"
            + " azure_tenant=?, azure_client=?, x509_cert_signer_keyid=?, ssh_cert_signer_keyid=?,"
            + " slack_channel=? WHERE name=?;";
    private static final String SQL_UPDATE_DOMAIN_MOD_TIMESTAMP = "UPDATE domain "
            + "SET modified=CURRENT_TIMESTAMP(3) WHERE name=?;";
    private static final String SQL_GET_DOMAIN_MOD_TIMESTAMP = "SELECT modified FROM domain WHERE name=?;";
    private static final String SQL_LIST_DOMAIN = "SELECT * FROM domain;";
    private static final String SQL_LIST_DOMAIN_PREFIX = "SELECT name, modified, enabled FROM domain WHERE name>=? AND name?;";
    private static final String SQL_LIST_DOMAIN_PREFIX_MODIFIED = "SELECT name, modified, enabled FROM domain "
            + "WHERE name>=? AND name?;";
    private static final String SQL_LIST_DOMAIN_ROLE_NAME_MEMBER = "SELECT domain.name FROM domain "
            + "JOIN role ON role.domain_id=domain.domain_id "
            + "JOIN role_member ON role_member.role_id=role.role_id "
            + "JOIN principal ON principal.principal_id=role_member.principal_id "
            + "WHERE principal.name=? AND role.name=?;";
    private static final String SQL_LIST_DOMAIN_ROLE_MEMBER = "SELECT domain.name FROM domain "
            + "JOIN role ON role.domain_id=domain.domain_id "
            + "JOIN role_member ON role_member.role_id=role.role_id "
            + "JOIN principal ON principal.principal_id=role_member.principal_id "
            + "WHERE principal.name=?;";
    private static final String SQL_LIST_DOMAIN_ROLE_NAME = "SELECT domain.name FROM domain "
            + "JOIN role ON role.domain_id=domain.domain_id WHERE role.name=?;";
    private static final String SQL_GET_ROLE = "SELECT * FROM role "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "WHERE domain.name=? AND role.name=?;";
    private static final String SQL_GET_ROLE_ID = "SELECT role_id FROM role WHERE domain_id=? AND name=?;";
    private static final String SQL_INSERT_ROLE = "INSERT INTO role (name, domain_id, trust, audit_enabled, self_serve,"
            + " member_expiry_days, token_expiry_mins, cert_expiry_mins, sign_algorithm, service_expiry_days,"
            + " member_review_days, service_review_days, group_review_days, review_enabled, notify_roles, user_authority_filter,"
            + " user_authority_expiration, description, group_expiry_days, delete_protection, last_reviewed_time,"
            + " max_members, self_renew, self_renew_mins, principal_domain_filter, notify_details)"
            + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);";
    private static final String SQL_UPDATE_ROLE = "UPDATE role SET trust=?, audit_enabled=?, self_serve=?, "
            + "member_expiry_days=?, token_expiry_mins=?, cert_expiry_mins=?, sign_algorithm=?, "
            + "service_expiry_days=?, member_review_days=?, service_review_days=?, group_review_days=?, review_enabled=?, notify_roles=?, "
            + "user_authority_filter=?, user_authority_expiration=?, description=?, group_expiry_days=?, "
            + "delete_protection=?, last_reviewed_time=?, max_members=?, self_renew=?, self_renew_mins=?, "
            + "principal_domain_filter=?, notify_details=? WHERE role_id=?;";
    private static final String SQL_DELETE_ROLE = "DELETE FROM role WHERE domain_id=? AND name=?;";
    private static final String SQL_UPDATE_ROLE_MOD_TIMESTAMP = "UPDATE role "
            + "SET modified=CURRENT_TIMESTAMP(3) WHERE role_id=?;";
    private static final String SQL_LIST_ROLE = "SELECT name FROM role WHERE domain_id=?;";
    private static final String SQL_COUNT_ROLE = "SELECT COUNT(*) FROM role WHERE domain_id=?;";
    private static final String SQL_GET_ROLE_MEMBER = "SELECT principal.principal_id, role_member.expiration, "
            + "role_member.review_reminder, role_member.req_principal, role_member.system_disabled FROM principal "
            + "JOIN role_member ON role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role.role_id=? AND principal.name=?;";
    private static final String SQL_GET_TEMP_ROLE_MEMBER = "SELECT principal.principal_id, role_member.expiration, "
            + "role_member.review_reminder, role_member.req_principal, role_member.system_disabled FROM principal "
            + "JOIN role_member ON role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role.role_id=? AND principal.name=? AND role_member.expiration=?;";
    private static final String SQL_GET_PENDING_ROLE_MEMBER = "SELECT principal.principal_id, pending_role_member.expiration, pending_role_member.review_reminder, pending_role_member.req_principal, pending_role_member.pending_state FROM principal "
            + "JOIN pending_role_member ON pending_role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=pending_role_member.role_id "
            + "WHERE role.role_id=? AND principal.name=?;";
    private static final String SQL_GET_TEMP_PENDING_ROLE_MEMBER = "SELECT principal.principal_id, pending_role_member.expiration, pending_role_member.review_reminder, pending_role_member.req_principal, pending_role_member.pending_state FROM principal "
            + "JOIN pending_role_member ON pending_role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=pending_role_member.role_id "
            + "WHERE role.role_id=? AND principal.name=? AND pending_role_member.expiration=?;";
    private static final String SQL_GET_PENDING_ROLE_MEMBER_STATE = "SELECT pending_role_member.pending_state FROM principal "
            + "JOIN pending_role_member ON pending_role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=pending_role_member.role_id "
            + "WHERE role.role_id=? AND principal.name=?;";
    private static final String SQL_STD_ROLE_MEMBER_EXISTS = "SELECT principal_id FROM role_member WHERE role_id=? AND principal_id=?;";
    private static final String SQL_PENDING_ROLE_MEMBER_EXISTS = "SELECT pending_state FROM pending_role_member WHERE role_id=? AND principal_id=?;";
    private static final String SQL_LIST_ROLE_MEMBERS = "SELECT principal.name, role_member.expiration, "
            + "role_member.review_reminder, role_member.active, role_member.audit_ref, role_member.system_disabled FROM principal "
            + "JOIN role_member ON role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=role_member.role_id WHERE role.role_id=?;";
    private static final String SQL_LIST_PENDING_ROLE_MEMBERS = "SELECT principal.name, pending_role_member.expiration, pending_role_member.review_reminder, pending_role_member.req_time, pending_role_member.audit_ref, pending_role_member.pending_state FROM principal "
            + "JOIN pending_role_member ON pending_role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=pending_role_member.role_id WHERE role.role_id=?;";
    private static final String SQL_COUNT_ROLE_MEMBERS = "SELECT COUNT(*) FROM role_member WHERE role_id=?;";
    private static final String SQL_GET_PRINCIPAL_ID = "SELECT principal_id FROM principal WHERE name=?;";
    private static final String SQL_INSERT_PRINCIPAL = "INSERT INTO principal (name) VALUES (?);";
    private static final String SQL_DELETE_PRINCIPAL = "DELETE FROM principal WHERE name=?;";
    private static final String SQL_DELETE_SUB_PRINCIPALS = "DELETE FROM principal WHERE name LIKE ?;";
    private static final String SQL_LIST_PRINCIPAL = "SELECT * FROM principal;";
    private static final String SQL_LIST_PRINCIPAL_DOMAIN = "SELECT * FROM principal WHERE name LIKE ? OR name LIKE ?;";
    private static final String SQL_LAST_INSERT_ID = "SELECT LAST_INSERT_ID();";
    private static final String SQL_INSERT_ROLE_MEMBER = "INSERT INTO role_member "
            + "(role_id, principal_id, expiration, review_reminder, active, audit_ref, req_principal) VALUES (?,?,?,?,?,?,?);";
    private static final String SQL_INSERT_PENDING_ROLE_MEMBER = "INSERT INTO pending_role_member "
            + "(role_id, principal_id, expiration, review_reminder, audit_ref, req_principal, pending_state) VALUES (?,?,?,?,?,?,?);";
    private static final String SQL_DELETE_ROLE_MEMBER = "DELETE FROM role_member WHERE role_id=? AND principal_id=?;";
    private static final String SQL_DELETE_EXPIRED_ROLE_MEMBER = "DELETE FROM role_member WHERE role_id=? AND principal_id=? AND expiration=?;";
    private static final String SQL_DELETE_PENDING_ROLE_MEMBER = "DELETE FROM pending_role_member WHERE role_id=? AND principal_id=?;";
    private static final String SQL_UPDATE_ROLE_MEMBER = "UPDATE role_member "
            + "SET expiration=?, review_reminder=?, active=?, audit_ref=?, req_principal=? WHERE role_id=? AND principal_id=?;";
    private static final String SQL_UPDATE_ROLE_MEMBER_DISABLED_STATE = "UPDATE role_member "
            + "SET system_disabled=?, audit_ref=?, req_principal=? WHERE role_id=? AND principal_id=?;";
    private static final String SQL_UPDATE_PENDING_ROLE_MEMBER = "UPDATE pending_role_member "
            + "SET expiration=?, review_reminder=?, audit_ref=?, req_time=CURRENT_TIMESTAMP(3), req_principal=? WHERE role_id=? AND principal_id=?;";
    private static final String SQL_INSERT_ROLE_AUDIT_LOG = "INSERT INTO role_audit_log "
            + "(role_id, admin, member, action, audit_ref) VALUES (?,?,?,?,?);";
    private static final String SQL_LIST_ROLE_AUDIT_LOGS = "SELECT * FROM role_audit_log WHERE role_id=?;";
    private static final String SQL_GET_POLICY = "SELECT * FROM policy "
            + "JOIN domain ON domain.domain_id=policy.domain_id WHERE domain.name=? AND policy.name=? AND policy.active=true;";
    private static final String SQL_GET_POLICY_VERSION = "SELECT * FROM policy "
            + "JOIN domain ON domain.domain_id=policy.domain_id WHERE domain.name=? AND policy.name=? AND policy.version=?;";
    private static final String SQL_INSERT_POLICY = "INSERT INTO policy (name, domain_id) VALUES (?,?);";
    private static final String SQL_INSERT_POLICY_VERSION = "INSERT INTO policy (name, domain_id, version, active) VALUES (?,?,?,?);";
    private static final String SQL_UPDATE_POLICY = "UPDATE policy SET name=? WHERE policy_id=?;";
    private static final String SQL_UPDATE_POLICY_MOD_TIMESTAMP = "UPDATE policy "
            + "SET modified=CURRENT_TIMESTAMP(3) WHERE policy_id=?;";
    private static final String SQL_SET_ACTIVE_POLICY_VERSION = "UPDATE policy SET active = CASE WHEN version=? then true ELSE false END WHERE domain_id=? AND name=?;";
    private static final String SQL_GET_ACTIVE_POLICY_ID = "SELECT policy_id FROM policy WHERE domain_id=? AND name=? AND active=true;";
    private static final String SQL_GET_POLICY_VERSION_ID = "SELECT policy_id FROM policy WHERE domain_id=? AND name=? AND version=?;";
    private static final String SQL_DELETE_POLICY = "DELETE FROM policy WHERE domain_id=? AND name=?;";
    private static final String SQL_DELETE_POLICY_VERSION = "DELETE FROM policy WHERE domain_id=? AND name=? AND version=? and active=false;";
    private static final String SQL_LIST_POLICY = "SELECT name FROM policy WHERE domain_id=? AND active=true";
    private static final String SQL_LIST_POLICY_VERSION = "SELECT version FROM policy WHERE domain_id=? AND name=?";
    private static final String SQL_COUNT_POLICY = "SELECT COUNT(*) FROM policy WHERE domain_id=?";
    private static final String SQL_LIST_ASSERTION = "SELECT * FROM assertion WHERE policy_id=?";
    private static final String SQL_COUNT_ASSERTION = "SELECT COUNT(*) FROM assertion WHERE policy_id=?";
    private static final String SQL_GET_ASSERTION = "SELECT * FROM assertion "
            + "JOIN policy ON assertion.policy_id=policy.policy_id "
            + "JOIN domain ON policy.domain_id=domain.domain_id "
            + "WHERE assertion.assertion_id=? AND domain.name=? AND policy.name=?;";
    private static final String SQL_CHECK_ASSERTION = "SELECT assertion_id FROM assertion "
            + "WHERE policy_id=? AND role=? AND resource=? AND action=? AND effect=?;";
    private static final String SQL_INSERT_ASSERTION = "INSERT INTO assertion "
            + "(policy_id, role, resource, action, effect) VALUES (?,?,?,?,?);";
    private static final String SQL_DELETE_ASSERTION = "DELETE FROM assertion "
            + "WHERE policy_id=? AND assertion_id=?;";
    private static final String SQL_GET_SERVICE = "SELECT * FROM service "
            + "JOIN domain ON domain.domain_id=service.domain_id WHERE domain.name=? AND service.name=?;";
    private static final String SQL_INSERT_SERVICE = "INSERT INTO service "
            + "(name, description, provider_endpoint, executable, svc_user, svc_group, domain_id) VALUES (?,?,?,?,?,?,?);";
    private static final String SQL_UPDATE_SERVICE = "UPDATE service SET "
            + "description=?, provider_endpoint=?, executable=?, svc_user=?, svc_group=?  WHERE service_id=?;";
    private static final String SQL_UPDATE_SERVICE_MOD_TIMESTAMP = "UPDATE service "
            + "SET modified=CURRENT_TIMESTAMP(3) WHERE service_id=?;";
    private static final String SQL_DELETE_SERVICE = "DELETE FROM service WHERE domain_id=? AND name=?;";
    private static final String SQL_GET_SERVICE_ID = "SELECT service_id FROM service WHERE domain_id=? AND name=?;";
    private static final String SQL_LIST_SERVICE = "SELECT name FROM service WHERE domain_id=?;";
    private static final String SQL_COUNT_SERVICE = "SELECT COUNT(*) FROM service WHERE domain_id=?;";
    private static final String SQL_LIST_PUBLIC_KEY = "SELECT * FROM public_key WHERE service_id=?;";
    private static final String SQL_COUNT_PUBLIC_KEY = "SELECT COUNT(*) FROM public_key WHERE service_id=?;";
    private static final String SQL_GET_PUBLIC_KEY = "SELECT key_value FROM public_key WHERE service_id=? AND key_id=?;";
    private static final String SQL_INSERT_PUBLIC_KEY = "INSERT INTO public_key "
            + "(service_id, key_id, key_value) VALUES (?,?,?);";
    private static final String SQL_UPDATE_PUBLIC_KEY = "UPDATE public_key SET key_value=? WHERE service_id=? AND key_id=?;";
    private static final String SQL_DELETE_PUBLIC_KEY = "DELETE FROM public_key WHERE service_id=? AND key_id=?;";
    private static final String SQL_LIST_SERVICE_HOST = "SELECT host.name FROM host "
            + "JOIN service_host ON service_host.host_id=host.host_id "
            + "WHERE service_host.service_id=?;";
    private static final String SQL_INSERT_SERVICE_HOST = "INSERT INTO service_host (service_id, host_id) VALUES (?,?);";
    private static final String SQL_DELETE_SERVICE_HOST = "DELETE FROM service_host WHERE service_id=? AND host_id=?;";
    private static final String SQL_GET_HOST_ID = "SELECT host_id FROM host WHERE name=?;";
    private static final String SQL_INSERT_HOST = "INSERT INTO host (name) VALUES (?);";
    private static final String SQL_INSERT_ENTITY = "INSERT INTO entity (domain_id, name, value) VALUES (?,?,?);";
    private static final String SQL_UPDATE_ENTITY = "UPDATE entity SET value=? WHERE domain_id=? AND name=?;";
    private static final String SQL_DELETE_ENTITY = "DELETE FROM entity WHERE domain_id=? AND name=?;";
    private static final String SQL_GET_ENTITY = "SELECT value FROM entity WHERE domain_id=? AND name=?;";
    private static final String SQL_LIST_ENTITY = "SELECT name FROM entity WHERE domain_id=?;";
    private static final String SQL_COUNT_ENTITY = "SELECT COUNT(*) FROM entity WHERE domain_id=?;";
    private static final String SQL_INSERT_DOMAIN_TEMPLATE = "INSERT INTO domain_template (domain_id, template) VALUES (?,?);";
    private static final String SQL_UPDATE_DOMAIN_TEMPLATE = "UPDATE domain_template SET current_version=? WHERE domain_id=? and template=?;";
    private static final String SQL_DELETE_DOMAIN_TEMPLATE = "DELETE FROM domain_template WHERE domain_id=? AND template=?;";
    private static final String SQL_LIST_DOMAIN_TEMPLATES = "SELECT * FROM domain_template WHERE domain_id=?;";
    private static final String SQL_LIST_DOMAIN_TEMPLATE = "SELECT template FROM domain_template "
            + "JOIN domain ON domain_template.domain_id=domain.domain_id "
            + "WHERE domain.name=?;";
    private static final String SQL_GET_DOMAIN_ENTITIES = "SELECT * FROM entity WHERE domain_id=?;";
    private static final String SQL_GET_DOMAIN_ROLES = "SELECT * FROM role WHERE domain_id=?;";
    private static final String SQL_GET_DOMAIN_ROLE_MEMBERS = "SELECT role.name, principal.name, role_member.expiration, "
            + "role_member.review_reminder, role_member.system_disabled FROM principal "
            + "JOIN role_member ON role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role.domain_id=?;";
    private static final String SQL_GET_PRINCIPAL_ROLES = "SELECT role.name, domain.name, role_member.expiration, "
            + "role_member.review_reminder, role_member.system_disabled FROM role_member "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "WHERE role_member.principal_id=?;";
    private static final String SQL_GET_PRINCIPAL_ROLES_DOMAIN = "SELECT role.name, domain.name, role_member.expiration, "
            + "role_member.review_reminder, role_member.system_disabled FROM role_member "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "WHERE role_member.principal_id=? AND domain.domain_id=?;";
    private static final String SQL_GET_REVIEW_OVERDUE_DOMAIN_ROLE_MEMBERS = "SELECT role.name, principal.name, role_member.expiration, "
            + "role_member.review_reminder, role_member.system_disabled FROM principal "
            + "JOIN role_member ON role_member.principal_id=principal.principal_id "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role.domain_id=? AND role_member.review_reminder < CURRENT_TIME;";
    private static final String SQL_GET_DOMAIN_POLICIES = "SELECT * FROM policy WHERE domain_id=?;";
    private static final String SQL_GET_DOMAIN_POLICY_ASSERTIONS = "SELECT policy.policy_id, "
            + "assertion.effect, assertion.action, assertion.role, assertion.resource, "
            + "assertion.assertion_id FROM assertion "
            + "JOIN policy ON policy.policy_id=assertion.policy_id "
            + "WHERE policy.domain_id=?;";
    private static final String SQL_GET_DOMAIN_SERVICES = "SELECT * FROM service WHERE domain_id=?;";
    private static final String SQL_GET_DOMAIN_SERVICES_HOSTS = "SELECT service.name, host.name FROM host "
            + "JOIN service_host ON host.host_id=service_host.host_id "
            + "JOIN service ON service.service_id=service_host.service_id "
            + "WHERE service.domain_id=?;";
    private static final String SQL_GET_DOMAIN_SERVICES_PUBLIC_KEYS = "SELECT service.name, "
            + "public_key.key_id, public_key.key_value FROM public_key "
            + "JOIN service ON service.service_id=public_key.service_id "
            + "WHERE service.domain_id=?;";
    private static final String SQL_LIST_POLICY_REFERENCING_ROLE = "SELECT name FROM policy "
            + "JOIN assertion ON policy.policy_id=assertion.policy_id "
            + "WHERE policy.domain_id=? AND assertion.role=?;";
    private static final String SQL_LIST_ROLE_ASSERTIONS = "SELECT assertion.role, assertion.resource, "
            + "assertion.action, assertion.effect, assertion.assertion_id, policy.domain_id, domain.name FROM assertion "
            + "JOIN policy ON assertion.policy_id=policy.policy_id "
            + "JOIN domain ON policy.domain_id=domain.domain_id";
    private static final String SQL_LIST_ROLE_ASSERTION_QUERY_ACTION = " WHERE assertion.action=?;";
    private static final String SQL_LIST_ROLE_ASSERTION_NO_ACTION = " WHERE assertion.action!='assume_role';";
    private static final String SQL_LIST_ROLE_PRINCIPALS = "SELECT role.domain_id, role.name AS role_name FROM principal "
            + "JOIN role_member ON principal.principal_id=role_member.principal_id "
            + "JOIN role ON role_member.role_id=role.role_id WHERE principal.name=? "
            + "AND principal.system_suspended=0 AND role_member.system_disabled=0 "
            + "AND (role_member.expiration IS NULL OR role_member.expiration > CURRENT_TIME);";
    private static final String SQL_LIST_ROLE_GROUP_PRINCIPALS = "SELECT principal.name, role.domain_id, "
            + "role.name AS role_name FROM principal "
            + "JOIN role_member ON principal.principal_id=role_member.principal_id "
            + "JOIN role ON role_member.role_id=role.role_id WHERE principal.name LIKE '%:group.%' "
            + "AND principal.system_suspended=0 AND role_member.system_disabled=0 "
            + "AND (role_member.expiration IS NULL OR role_member.expiration > CURRENT_TIME);";
    private static final String SQL_LIST_GROUP_FOR_PRINCIPAL = "SELECT principal_group.name, domain.name AS domain_name "
            + "FROM principal_group_member  JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "JOIN domain ON domain.domain_id=principal_group.domain_id JOIN principal ON principal.principal_id=principal_group_member.principal_id "
            + "WHERE principal.name=? AND principal.system_suspended=0 AND principal_group_member.system_disabled=0 "
            + "AND (principal_group_member.expiration IS NULL OR principal_group_member.expiration > CURRENT_TIME);";
    private static final String SQL_LIST_TRUSTED_STANDARD_ROLES = "SELECT role.domain_id, role.name, "
            + "policy.domain_id AS assert_domain_id, assertion.role FROM role "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "JOIN assertion ON assertion.resource=CONCAT(domain.name, \":role.\", role.name) "
            + "JOIN policy ON policy.policy_id=assertion.policy_id "
            + "WHERE assertion.action='assume_role';";
    private static final String SQL_LIST_TRUSTED_WILDCARD_ROLES = "SELECT role.domain_id, role.name, "
            + "policy.domain_id AS assert_domain_id, assertion.role FROM role "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "JOIN assertion ON assertion.resource=CONCAT(\"*:role.\", role.name) "
            + "JOIN policy ON policy.policy_id=assertion.policy_id "
            + "WHERE assertion.action='assume_role';";
    private static final String SQL_LIST_TRUSTED_ROLES_WITH_WILDCARD = "SELECT domain.name, role.name FROM role "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "WHERE domain.name LIKE ? AND role.name LIKE ? AND role.trust=?;";
    private static final String SQL_GET_QUOTA = "SELECT * FROM quota WHERE domain_id=?;";
    private static final String SQL_INSERT_QUOTA = "INSERT INTO quota (domain_id, role, role_member, "
            + "policy, assertion, service, service_host, public_key, entity, subdomain, principal_group, principal_group_member) "
            + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?);";
    private static final String SQL_UPDATE_QUOTA = "UPDATE quota SET role=?, role_member=?, "
            + "policy=?, assertion=?, service=?, service_host=?, public_key=?, entity=?, "
            + "subdomain=?, principal_group=?, principal_group_member=?  WHERE domain_id=?;";
    private static final String SQL_DELETE_QUOTA = "DELETE FROM quota WHERE domain_id=?;";
    private static final String SQL_PENDING_ORG_AUDIT_ROLE_MEMBER_LIST = "SELECT do.name AS domain, ro.name AS role, "
            + "principal.name AS member, rmo.expiration, rmo.review_reminder, rmo.audit_ref, rmo.req_time, rmo.req_principal, rmo.pending_state "
            + "FROM principal JOIN pending_role_member rmo "
            + "ON rmo.principal_id=principal.principal_id JOIN role ro ON ro.role_id=rmo.role_id JOIN domain do ON ro.domain_id=do.domain_id "
            + "WHERE ro.audit_enabled=true AND ro.domain_id IN ( select domain_id FROM domain WHERE org IN ( "
            + "SELECT DISTINCT role.name AS org FROM role_member JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role_member.principal_id=? AND role.domain_id=?) ) order by do.name, ro.name, principal.name;";
    private static final String SQL_PENDING_DOMAIN_AUDIT_ROLE_MEMBER_LIST = "SELECT do.name AS domain, ro.name AS role, "
            + "principal.name AS member, rmo.expiration, rmo.review_reminder, rmo.audit_ref, rmo.req_time, rmo.req_principal, rmo.pending_state "
            + "FROM principal JOIN pending_role_member rmo "
            + "ON rmo.principal_id=principal.principal_id JOIN role ro ON ro.role_id=rmo.role_id JOIN domain do ON ro.domain_id=do.domain_id "
            + "WHERE ro.audit_enabled=true AND ro.domain_id IN ( select domain_id FROM domain WHERE name IN ( "
            + "SELECT DISTINCT role.name AS domain_name FROM role_member JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role_member.principal_id=? AND role.domain_id=?) ) order by do.name, ro.name, principal.name;";
    private static final String SQL_PENDING_DOMAIN_ADMIN_ROLE_MEMBER_LIST = "SELECT do.name AS domain, ro.name AS role, "
            + "principal.name AS member, rmo.expiration, rmo.review_reminder, rmo.audit_ref, rmo.req_time, rmo.req_principal, rmo.pending_state "
            + "FROM principal JOIN pending_role_member rmo "
            + "ON rmo.principal_id=principal.principal_id JOIN role ro ON ro.role_id=rmo.role_id JOIN domain do ON ro.domain_id=do.domain_id "
            + "WHERE (ro.self_serve=true OR ro.review_enabled=true) AND ro.domain_id IN ( SELECT domain.domain_id FROM domain JOIN role "
            + "ON role.domain_id=domain.domain_id JOIN role_member ON role.role_id=role_member.role_id "
            + "WHERE role_member.principal_id=? AND role_member.active=true AND role.name='admin' ) "
            + "order by do.name, ro.name, principal.name;";
    private static final String SQL_PENDING_DOMAIN_ROLE_MEMBER_LIST = "SELECT do.name AS domain, ro.name AS role, "
            + "principal.name AS member, rmo.expiration, rmo.review_reminder, rmo.audit_ref, rmo.req_time, rmo.req_principal, rmo.pending_state "
            + "FROM principal JOIN pending_role_member rmo "
            + "ON rmo.principal_id=principal.principal_id JOIN role ro ON ro.role_id=rmo.role_id JOIN domain do ON ro.domain_id=do.domain_id "
            + "WHERE ro.domain_id=? AND (ro.self_serve=true OR ro.review_enabled=true OR ro.audit_enabled=true) "
            + "order by do.name, ro.name, principal.name;";
    private static final String SQL_PENDING_ALL_DOMAIN_ROLE_MEMBER_LIST = "SELECT do.name AS domain, ro.name AS role, "
            + "principal.name AS member, rmo.expiration, rmo.review_reminder, rmo.audit_ref, rmo.req_time, rmo.req_principal, rmo.pending_state "
            + "FROM principal JOIN pending_role_member rmo "
            + "ON rmo.principal_id=principal.principal_id JOIN role ro ON ro.role_id=rmo.role_id JOIN domain do ON ro.domain_id=do.domain_id "
            + "WHERE (ro.self_serve=true OR ro.review_enabled=true OR ro.audit_enabled=true)"
            + "order by do.name, ro.name, principal.name;";
    private static final String SQL_AUDIT_ENABLED_PENDING_MEMBERSHIP_REMINDER_ENTRIES =
              "SELECT distinct d.org, d.name FROM pending_role_member rm "
            + "JOIN role r ON r.role_id=rm.role_id JOIN domain d ON r.domain_id=d.domain_id "
            + "WHERE r.audit_enabled=true AND rm.last_notified_time=? AND rm.server=?;";
    private static final String SQL_ADMIN_PENDING_MEMBERSHIP_REMINDER_DOMAINS =
              "SELECT distinct d.name FROM pending_role_member rm "
            + "JOIN role r ON r.role_id=rm.role_id "
            + "JOIN domain d ON r.domain_id=d.domain_id WHERE (r.self_serve=true OR r.review_enabled=true) AND rm.last_notified_time=? AND rm.server=?;";
    private static final String SQL_GET_EXPIRED_PENDING_ROLE_MEMBERS = "SELECT d.name, r.name, p.name, prm.expiration, prm.review_reminder, prm.audit_ref, prm.req_time, prm.req_principal, prm.pending_state "
            + "FROM principal p JOIN pending_role_member prm "
            + "ON prm.principal_id=p.principal_id JOIN role r ON prm.role_id=r.role_id JOIN domain d ON d.domain_id=r.domain_id "
            + "WHERE prm.req_time < (CURRENT_TIME - INTERVAL ? DAY);";
    private static final String SQL_UPDATE_PENDING_ROLE_MEMBERS_NOTIFICATION_TIMESTAMP = "UPDATE pending_role_member SET last_notified_time=?, server=? "
            + "WHERE DAYOFWEEK(req_time)=DAYOFWEEK(?) AND (last_notified_time IS NULL || last_notified_time < (CURRENT_TIME - INTERVAL ? DAY));";
    private static final String SQL_UPDATE_ROLE_MEMBERS_EXPIRY_NOTIFICATION_TIMESTAMP =
              "UPDATE role_member SET last_notified_time=?, server=? "
            + "WHERE expiration > CURRENT_TIME AND DATEDIFF(expiration, CURRENT_TIME) IN (0,1,3,7,14,21,28);";
    private static final String SQL_LIST_NOTIFY_TEMPORARY_ROLE_MEMBERS = "SELECT domain.name AS domain_name, role.name AS role_name, "
            + "principal.name AS principal_name, role_member.expiration, role_member.review_reminder, role.notify_roles, role.notify_details FROM role_member "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "JOIN principal ON principal.principal_id=role_member.principal_id "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "WHERE role_member.last_notified_time=? AND role_member.server=?;";
    private static final String SQL_UPDATE_ROLE_MEMBERS_REVIEW_NOTIFICATION_TIMESTAMP =
              "UPDATE role_member SET review_last_notified_time=?, review_server=? "
            + "WHERE review_reminder > CURRENT_TIME AND expiration IS NULL AND DATEDIFF(review_reminder, CURRENT_TIME) IN (0,1,3,7,14,21,28);";
    private static final String SQL_LIST_NOTIFY_REVIEW_ROLE_MEMBERS = "SELECT domain.name AS domain_name, role.name AS role_name, "
            + "principal.name AS principal_name, role_member.expiration, role_member.review_reminder, role.notify_roles, role.notify_details FROM role_member "
            + "JOIN role ON role.role_id=role_member.role_id "
            + "JOIN principal ON principal.principal_id=role_member.principal_id "
            + "JOIN domain ON domain.domain_id=role.domain_id "
            + "WHERE role_member.review_last_notified_time=? AND role_member.review_server=?;";
    private static final String SQL_UPDATE_ROLE_REVIEW_TIMESTAMP = "UPDATE role SET last_reviewed_time=CURRENT_TIMESTAMP(3) WHERE role_id=?;";
    private static final String SQL_LIST_ROLES_WITH_RESTRICTIONS = "SELECT domain.name as domain_name, "
            + "role.name as role_name, domain.user_authority_filter as domain_user_authority_filter, domain.member_expiry_days "
            + "FROM role JOIN domain ON role.domain_id=domain.domain_id WHERE role.user_authority_filter!='' "
            + "OR role.user_authority_expiration!='' OR domain.user_authority_filter!='';";
    private static final String SQL_GET_GROUP = "SELECT * FROM principal_group "
            + "JOIN domain ON domain.domain_id=principal_group.domain_id "
            + "WHERE domain.name=? AND principal_group.name=?;";
    private static final String SQL_INSERT_GROUP = "INSERT INTO principal_group (name, domain_id, audit_enabled, self_serve, "
            + "review_enabled, notify_roles, user_authority_filter, user_authority_expiration, member_expiry_days, "
            + "service_expiry_days, delete_protection, last_reviewed_time, max_members, self_renew, self_renew_mins, "
            + "principal_domain_filter, notify_details) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);";
    private static final String SQL_UPDATE_GROUP = "UPDATE principal_group SET audit_enabled=?, self_serve=?, "
            + "review_enabled=?, notify_roles=?, user_authority_filter=?, user_authority_expiration=?, "
            + "member_expiry_days=?, service_expiry_days=?, delete_protection=?, last_reviewed_time=?, "
            + "max_members=?, self_renew=?, self_renew_mins=?, principal_domain_filter=?, notify_details=? WHERE group_id=?;";
    private static final String SQL_GET_GROUP_ID = "SELECT group_id FROM principal_group WHERE domain_id=? AND name=?;";
    private static final String SQL_DELETE_GROUP = "DELETE FROM principal_group WHERE domain_id=? AND name=?;";
    private static final String SQL_UPDATE_GROUP_MOD_TIMESTAMP = "UPDATE principal_group "
            + "SET modified=CURRENT_TIMESTAMP(3) WHERE group_id=?;";
    private static final String SQL_COUNT_GROUP = "SELECT COUNT(*) FROM principal_group WHERE domain_id=?;";
    private static final String SQL_GET_GROUP_MEMBER = "SELECT principal.principal_id, principal_group_member.expiration, "
            + "principal_group_member.req_principal, principal_group_member.system_disabled FROM principal "
            + "JOIN principal_group_member ON principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "WHERE principal_group.group_id=? AND principal.name=?;";
    private static final String SQL_GET_TEMP_GROUP_MEMBER = "SELECT principal.principal_id, principal_group_member.expiration, "
            + "principal_group_member.req_principal, principal_group_member.system_disabled FROM principal "
            + "JOIN principal_group_member ON principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "WHERE principal_group.group_id=? AND principal.name=? AND principal_group_member.expiration=?;";
    private static final String SQL_GET_PENDING_GROUP_MEMBER = "SELECT principal.principal_id, "
            + "pending_principal_group_member.expiration, pending_principal_group_member.req_principal, pending_principal_group_member.pending_state FROM principal "
            + "JOIN pending_principal_group_member ON pending_principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=pending_principal_group_member.group_id "
            + "WHERE principal_group.group_id=? AND principal.name=?;";
    private static final String SQL_GET_TEMP_PENDING_GROUP_MEMBER = "SELECT principal.principal_id, "
            + "pending_principal_group_member.expiration, pending_principal_group_member.req_principal, pending_principal_group_member.pending_state FROM principal "
            + "JOIN pending_principal_group_member ON pending_principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=pending_principal_group_member.group_id "
            + "WHERE principal_group.group_id=? AND principal.name=? AND pending_principal_group_member.expiration=?;";
    private static final String SQL_GET_PENDING_GROUP_MEMBER_STATE = "SELECT pending_principal_group_member.pending_state FROM principal "
            + "JOIN pending_principal_group_member ON pending_principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=pending_principal_group_member.group_id "
            + "WHERE principal_group.group_id=? AND principal.name=?;";
    private static final String SQL_LIST_GROUP_AUDIT_LOGS = "SELECT * FROM principal_group_audit_log WHERE group_id=?;";
    private static final String SQL_UPDATE_GROUP_REVIEW_TIMESTAMP = "UPDATE principal_group SET last_reviewed_time=CURRENT_TIMESTAMP(3) WHERE group_id=?;";
    private static final String SQL_LIST_GROUPS_WITH_RESTRICTIONS = "SELECT domain.name as domain_name, "
            + "principal_group.name as group_name, domain.user_authority_filter as domain_user_authority_filter FROM principal_group "
            + "JOIN domain ON principal_group.domain_id=domain.domain_id WHERE principal_group.user_authority_filter!='' "
            + "OR principal_group.user_authority_expiration!='' OR domain.user_authority_filter!='';";
    private static final String SQL_LIST_GROUP_MEMBERS = "SELECT principal.name, principal_group_member.expiration, "
            + "principal_group_member.active, principal_group_member.audit_ref, principal_group_member.system_disabled FROM principal "
            + "JOIN principal_group_member ON principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id WHERE principal_group.group_id=?;";
    private static final String SQL_LIST_PENDING_GROUP_MEMBERS = "SELECT principal.name, pending_principal_group_member.expiration, "
            + "pending_principal_group_member.req_time, pending_principal_group_member.audit_ref, pending_principal_group_member.pending_state FROM principal "
            + "JOIN pending_principal_group_member ON pending_principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=pending_principal_group_member.group_id WHERE principal_group.group_id=?;";
    private static final String SQL_COUNT_GROUP_MEMBERS = "SELECT COUNT(*) FROM principal_group_member WHERE group_id=?;";
    private static final String SQL_STD_GROUP_MEMBER_EXISTS = "SELECT principal_id FROM principal_group_member WHERE group_id=? AND principal_id=?;";
    private static final String SQL_PENDING_GROUP_MEMBER_EXISTS = "SELECT pending_state FROM pending_principal_group_member WHERE group_id=? AND principal_id=?;";
    private static final String SQL_UPDATE_GROUP_MEMBER = "UPDATE principal_group_member "
            + "SET expiration=?, active=?, audit_ref=?, req_principal=? WHERE group_id=? AND principal_id=?;";
    private static final String SQL_UPDATE_GROUP_MEMBER_DISABLED_STATE = "UPDATE principal_group_member "
            + "SET system_disabled=?, audit_ref=?, req_principal=? WHERE group_id=? AND principal_id=?;";
    private static final String SQL_UPDATE_PENDING_GROUP_MEMBER = "UPDATE pending_principal_group_member "
            + "SET expiration=?, audit_ref=?, req_time=CURRENT_TIMESTAMP(3), req_principal=? WHERE group_id=? AND principal_id=?;";
    private static final String SQL_INSERT_GROUP_MEMBER = "INSERT INTO principal_group_member "
            + "(group_id, principal_id, expiration, active, audit_ref, req_principal) VALUES (?,?,?,?,?,?);";
    private static final String SQL_INSERT_PENDING_GROUP_MEMBER = "INSERT INTO pending_principal_group_member "
            + "(group_id, principal_id, expiration, audit_ref, req_principal, pending_state) VALUES (?,?,?,?,?,?);";
    private static final String SQL_DELETE_GROUP_MEMBER = "DELETE FROM principal_group_member WHERE group_id=? AND principal_id=?;";
    private static final String SQL_DELETE_EXPIRED_GROUP_MEMBER = "DELETE FROM principal_group_member WHERE group_id=? AND principal_id=? AND expiration=?;";
    private static final String SQL_DELETE_PENDING_GROUP_MEMBER = "DELETE FROM pending_principal_group_member WHERE group_id=? AND principal_id=?;";
    private static final String SQL_INSERT_GROUP_AUDIT_LOG = "INSERT INTO principal_group_audit_log "
            + "(group_id, admin, member, action, audit_ref) VALUES (?,?,?,?,?);";
    private static final String SQL_GET_PRINCIPAL_GROUPS = "SELECT principal_group.name, domain.name, principal_group_member.expiration, "
            + "principal_group_member.system_disabled FROM principal_group_member "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "JOIN domain ON domain.domain_id=principal_group.domain_id "
            + "WHERE principal_group_member.principal_id=?;";
    private static final String SQL_GET_PRINCIPAL_GROUPS_DOMAIN = "SELECT principal_group.name, domain.name, principal_group_member.expiration, "
            + "principal_group_member.system_disabled FROM principal_group_member "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "JOIN domain ON domain.domain_id=principal_group.domain_id "
            + "WHERE principal_group_member.principal_id=? AND domain.domain_id=?;";
    private static final String SQL_GET_DOMAIN_GROUPS = "SELECT * FROM principal_group WHERE domain_id=?;";
    private static final String SQL_GET_DOMAIN_GROUP_MEMBERS = "SELECT principal_group.name, principal.name, "
            + "principal_group_member.expiration, principal_group_member.system_disabled FROM principal "
            + "JOIN principal_group_member ON principal_group_member.principal_id=principal.principal_id "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "WHERE principal_group.domain_id=?;";
    private static final String SQL_PENDING_ORG_AUDIT_GROUP_MEMBER_LIST = "SELECT do.name AS domain, grp.name AS group_name, "
            + "principal.name AS member, pgm.expiration, pgm.audit_ref, pgm.req_time, pgm.req_principal, pgm.pending_state "
            + "FROM principal JOIN pending_principal_group_member pgm "
            + "ON pgm.principal_id=principal.principal_id JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain do ON grp.domain_id=do.domain_id "
            + "WHERE grp.audit_enabled=true AND grp.domain_id IN ( select domain_id FROM domain WHERE org IN ( "
            + "SELECT DISTINCT role.name AS org FROM role_member JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role_member.principal_id=? AND role.domain_id=?) ) order by do.name, grp.name, principal.name;";
    private static final String SQL_PENDING_DOMAIN_AUDIT_GROUP_MEMBER_LIST = "SELECT do.name AS domain, grp.name AS group_name, "
            + "principal.name AS member, pgm.expiration, pgm.audit_ref, pgm.req_time, pgm.req_principal, pgm.pending_state "
            + "FROM principal JOIN pending_principal_group_member pgm "
            + "ON pgm.principal_id=principal.principal_id JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain do ON grp.domain_id=do.domain_id "
            + "WHERE grp.audit_enabled=true AND grp.domain_id IN ( select domain_id FROM domain WHERE name IN ( "
            + "SELECT DISTINCT role.name AS domain_name FROM role_member JOIN role ON role.role_id=role_member.role_id "
            + "WHERE role_member.principal_id=? AND role.domain_id=?) ) order by do.name, grp.name, principal.name;";
    private static final String SQL_PENDING_DOMAIN_ADMIN_GROUP_MEMBER_LIST = "SELECT do.name AS domain, grp.name AS group_name, "
            + "principal.name AS member, pgm.expiration, pgm.audit_ref, pgm.req_time, pgm.req_principal, pgm.pending_state "
            + "FROM principal JOIN pending_principal_group_member pgm "
            + "ON pgm.principal_id=principal.principal_id JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain do ON grp.domain_id=do.domain_id "
            + "WHERE (grp.self_serve=true OR grp.review_enabled=true) AND grp.domain_id IN ( SELECT domain.domain_id FROM domain JOIN role "
            + "ON role.domain_id=domain.domain_id JOIN role_member ON role.role_id=role_member.role_id "
            + "WHERE role_member.principal_id=? AND role_member.active=true AND role.name='admin' ) "
            + "order by do.name, grp.name, principal.name;";
    private static final String SQL_PENDING_DOMAIN_GROUP_MEMBER_LIST = "SELECT do.name AS domain, grp.name AS group_name, "
            + "principal.name AS member, pgm.expiration, pgm.audit_ref, pgm.req_time, pgm.req_principal, pgm.pending_state "
            + "FROM principal JOIN pending_principal_group_member pgm "
            + "ON pgm.principal_id=principal.principal_id JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain do ON grp.domain_id=do.domain_id "
            + "WHERE grp.domain_id=? AND (grp.self_serve=true OR grp.review_enabled=true OR grp.audit_enabled=true) "
            + "order by do.name, grp.name, principal.name;";
    private static final String SQL_PENDING_ALL_DOMAIN_GROUP_MEMBER_LIST = "SELECT do.name AS domain, grp.name AS group_name, "
            + "principal.name AS member, pgm.expiration, pgm.audit_ref, pgm.req_time, pgm.req_principal, pgm.pending_state "
            + "FROM principal JOIN pending_principal_group_member pgm "
            + "ON pgm.principal_id=principal.principal_id JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain do ON grp.domain_id=do.domain_id "
            + "WHERE (grp.self_serve=true OR grp.review_enabled=true OR grp.audit_enabled=true) "
            + "order by do.name, grp.name, principal.name;";
    private static final String SQL_GET_EXPIRED_PENDING_GROUP_MEMBERS = "SELECT d.name, grp.name, p.name, pgm.expiration, pgm.audit_ref, pgm.req_time, pgm.req_principal, pgm.pending_state "
            + "FROM principal p JOIN pending_principal_group_member pgm "
            + "ON pgm.principal_id=p.principal_id JOIN principal_group grp ON pgm.group_id=grp.group_id JOIN domain d ON d.domain_id=grp.domain_id "
            + "WHERE pgm.req_time < (CURRENT_TIME - INTERVAL ? DAY);";
    private static final String SQL_AUDIT_ENABLED_PENDING_GROUP_MEMBERSHIP_REMINDER_ENTRIES = "SELECT distinct d.org, d.name FROM pending_principal_group_member pgm "
            + "JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain d ON grp.domain_id=d.domain_id "
            + "WHERE grp.audit_enabled=true AND pgm.last_notified_time=? AND pgm.server=?;";
    private static final String SQL_UPDATE_PENDING_GROUP_MEMBERS_NOTIFICATION_TIMESTAMP = "UPDATE pending_principal_group_member SET last_notified_time=?, server=? "
            + "WHERE DAYOFWEEK(req_time)=DAYOFWEEK(?) AND (last_notified_time IS NULL || last_notified_time < (CURRENT_TIME - INTERVAL ? DAY));";
    private static final String SQL_ADMIN_PENDING_GROUP_MEMBERSHIP_REMINDER_DOMAINS = "SELECT distinct d.name FROM pending_principal_group_member pgm "
            + "JOIN principal_group grp ON grp.group_id=pgm.group_id JOIN domain d ON grp.domain_id=d.domain_id "
            + "WHERE grp.self_serve=true AND pgm.last_notified_time=? AND pgm.server=?;";
    private static final String SQL_UPDATE_GROUP_MEMBERS_EXPIRY_NOTIFICATION_TIMESTAMP =
              "UPDATE principal_group_member SET last_notified_time=?, server=? "
            + "WHERE expiration > CURRENT_TIME AND DATEDIFF(expiration, CURRENT_TIME) IN (0,1,3,7,14,21,28);";
    private static final String SQL_LIST_NOTIFY_TEMPORARY_GROUP_MEMBERS = "SELECT domain.name AS domain_name, principal_group.name AS group_name, "
            + "principal.name AS principal_name, principal_group_member.expiration, principal_group.notify_roles, "
            + "principal_group.notify_details FROM principal_group_member "
            + "JOIN principal_group ON principal_group.group_id=principal_group_member.group_id "
            + "JOIN principal ON principal.principal_id=principal_group_member.principal_id "
            + "JOIN domain ON domain.domain_id=principal_group.domain_id "
            + "WHERE principal_group_member.last_notified_time=? AND principal_group_member.server=?;";
    private static final String SQL_UPDATE_PRINCIPAL = "UPDATE principal SET system_suspended=? WHERE name=?;";
    private static final String SQL_GET_PRINCIPAL = "SELECT * FROM principal WHERE name=?;";
    private static final String SQL_GET_PRINCIPALS = "SELECT * FROM principal WHERE (system_suspended & ?) > 0;";
    private static final String SQL_INSERT_ROLE_TAG = "INSERT INTO role_tags"
            + "(role_id, role_tags.key, role_tags.value) VALUES (?,?,?);";
    private static final String SQL_ROLE_TAG_COUNT = "SELECT COUNT(*) FROM role_tags WHERE role_id=?";
    private static final String SQL_DELETE_ROLE_TAG = "DELETE FROM role_tags WHERE role_id=? AND role_tags.key=?;";
    private static final String SQL_GET_ROLE_TAGS = "SELECT rt.key, rt.value FROM role_tags rt "
            + "JOIN role r ON rt.role_id = r.role_id JOIN domain ON domain.domain_id=r.domain_id "
            + "WHERE domain.name=? AND r.name=?";
    private static final String SQL_GET_DOMAIN_ROLE_TAGS = "SELECT r.name, rt.key, rt.value FROM role_tags rt "
            + "JOIN role r ON rt.role_id = r.role_id JOIN domain ON domain.domain_id=r.domain_id "
            + "WHERE domain.name=?";
    private static final String SQL_INSERT_DOMAIN_TAG = "INSERT INTO domain_tags"
            + "(domain_id, domain_tags.key, domain_tags.value) VALUES (?,?,?);";
    private static final String SQL_DOMAIN_TAG_COUNT = "SELECT COUNT(*) FROM domain_tags WHERE domain_id=?";
    private static final String SQL_DELETE_DOMAIN_TAG = "DELETE FROM domain_tags WHERE domain_id=? AND domain_tags.key=?;";
    private static final String SQL_GET_DOMAIN_TAGS = "SELECT dt.key, dt.value FROM domain_tags dt WHERE dt.domain_id=?";
    private static final String SQL_LOOKUP_DOMAIN_BY_TAG_KEY = "SELECT d.name FROM domain d "
            + "JOIN domain_tags dt ON dt.domain_id = d.domain_id WHERE dt.key=?";
    private static final String SQL_LOOKUP_DOMAIN_BY_TAG_KEY_VAL = "SELECT d.name FROM domain d "
            + "JOIN domain_tags dt ON dt.domain_id = d.domain_id WHERE dt.key=? AND dt.value=?";
    private static final String SQL_INSERT_GROUP_TAG = "INSERT INTO group_tags"
            + "(group_id, group_tags.key, group_tags.value) VALUES (?,?,?);";
    private static final String SQL_GROUP_TAG_COUNT = "SELECT COUNT(*) FROM group_tags WHERE group_id=?";
    private static final String SQL_DELETE_GROUP_TAG = "DELETE FROM group_tags WHERE group_id=? AND group_tags.key=?;";
    private static final String SQL_GET_GROUP_TAGS = "SELECT gt.key, gt.value FROM group_tags gt "
            + "JOIN principal_group g ON gt.group_id = g.group_id JOIN domain ON domain.domain_id=g.domain_id "
            + "WHERE domain.name=? AND g.name=?";
    private static final String SQL_GET_DOMAIN_GROUP_TAGS = "SELECT g.name, gt.key, gt.value FROM group_tags gt "
            + "JOIN principal_group g ON gt.group_id = g.group_id JOIN domain ON domain.domain_id=g.domain_id "
            + "WHERE domain.name=?";
    private static final String SQL_INSERT_SERVICE_TAG = "INSERT INTO service_tags"
            + "(service_id, service_tags.key, service_tags.value) VALUES (?,?,?);";
    private static final String SQL_SERVICE_TAG_COUNT = "SELECT COUNT(*) FROM service_tags WHERE service_id=?";
    private static final String SQL_DELETE_SERVICE_TAG = "DELETE FROM service_tags WHERE service_id=? AND service_tags.key=?;";
    private static final String SQL_GET_SERVICE_TAGS = "SELECT st.key, st.value FROM service_tags st "
            + "JOIN service s ON st.service_id = s.service_id JOIN domain ON domain.domain_id=s.domain_id "
            + "WHERE domain.name=? AND s.name=?";
    private static final String SQL_GET_DOMAIN_SERVICE_TAGS = "SELECT s.name, st.key, st.value FROM service_tags st "
            + "JOIN service s ON st.service_id = s.service_id JOIN domain ON domain.domain_id=s.domain_id "
            + "WHERE domain.name=?";
    private static final String SQL_GET_ASSERTION_CONDITIONS = "SELECT assertion_condition.condition_id, "
            + "assertion_condition.key, assertion_condition.operator, assertion_condition.value "
            + "FROM assertion_condition WHERE assertion_condition.assertion_id=? ORDER BY assertion_condition.condition_id;";
    private static final String SQL_GET_ASSERTION_CONDITION = "SELECT assertion_condition.key, assertion_condition.operator, assertion_condition.value, condition_id "
            + "FROM assertion_condition WHERE assertion_id=? AND condition_id=? ORDER BY condition_id;";
    private static final String SQL_COUNT_ASSERTION_CONDITIONS = "SELECT count(1) FROM assertion_condition WHERE assertion_id=?;";
    private static final String SQL_INSERT_ASSERTION_CONDITION = "INSERT INTO assertion_condition (assertion_id,condition_id,`key`,operator,`value`) VALUES (?,?,?,?,?);";
    private static final String SQL_DELETE_ASSERTION_CONDITION = "DELETE FROM assertion_condition WHERE assertion_id=? AND condition_id=?;";
    private static final String SQL_DELETE_ASSERTION_CONDITIONS = "DELETE FROM assertion_condition WHERE assertion_id=?;";
    private static final String SQL_GET_NEXT_CONDITION_ID = "SELECT IFNULL(MAX(condition_id)+1, 1) FROM assertion_condition WHERE assertion_id=?;";
    private static final String SQL_GET_DOMAIN_POLICY_ASSERTIONS_CONDITIONS = "SELECT assertion.assertion_id, "
            + "assertion_condition.condition_id, assertion_condition.key, assertion_condition.operator, assertion_condition.value "
            + "FROM assertion_condition JOIN assertion ON assertion_condition.assertion_id=assertion.assertion_id "
            + "JOIN policy ON policy.policy_id=assertion.policy_id "
            + "WHERE policy.domain_id=? ORDER BY assertion.assertion_id, assertion_condition.condition_id;";

    private static final String SQL_GET_POLICY_ASSERTIONS_CONDITIONS = "SELECT assertion.assertion_id, "
            + "assertion_condition.condition_id, assertion_condition.key, assertion_condition.operator, assertion_condition.value "
            + "FROM assertion_condition JOIN assertion ON assertion_condition.assertion_id=assertion.assertion_id "
            + "JOIN policy ON policy.policy_id=assertion.policy_id "
            + "WHERE policy.policy_id=? ORDER BY assertion.assertion_id, assertion_condition.condition_id;";

    private static final String SQL_GET_OBJECT_SYSTEM_COUNT = "SELECT COUNT(*) FROM ";
    private static final String SQL_GET_OBJECT_DOMAIN_COUNT = "SELECT COUNT(*) FROM ";
    private static final String SQL_GET_OBJECT_DOMAIN_COUNT_QUERY = " WHERE domain_id=?";
    private static final String SQL_GET_DOMAIN_ASSERTION_COUNT = "SELECT COUNT(*) from assertion JOIN policy on policy.policy_id=assertion.policy_id WHERE policy.domain_id=?;";
    private static final String SQL_GET_DOMAIN_ROLE_MEMBER_COUNT = "SELECT COUNT(*) from role_member JOIN role on role.role_id=role_member.role_id WHERE role.domain_id=?;";
    private static final String SQL_GET_DOMAIN_GROUP_MEMBER_COUNT = "SELECT COUNT(*) from principal_group_member JOIN principal_group on principal_group.group_id=principal_group_member.group_id WHERE principal_group.domain_id=?;";
    private static final String SQL_GET_DOMAIN_SERVICE_HOST_COUNT = "SELECT COUNT(*) from service_host JOIN service on service_host.service_id=service.service_id WHERE service.domain_id=?;";
    private static final String SQL_GET_DOMAIN_SERVICE_PUBLIC_KEY_COUNT = "SELECT COUNT(*) from public_key JOIN service on public_key.service_id=service.service_id WHERE service.domain_id=?;";
    private static final String SQL_GET_DOMAIN_PREFIX_COUNT = "SELECT COUNT(*) FROM domain WHERE name>=? AND name -1 AND M.expiration is not null"
            + " AND (D.member_purge_expiry_days = 0 AND CURRENT_DATE() >= DATE_ADD(DATE(M.expiration), INTERVAL ? DAY)"
            + " OR D.member_purge_expiry_days != 0 AND CURRENT_DATE() >= DATE_ADD(DATE(M.expiration), INTERVAL D.member_purge_expiry_days DAY))"
            + " limit ? offset ?";
    private static final String GET_ALL_EXPIRED_GROUP_MEMBERS = "SELECT D.name as domain_name, G.name as group_name, M.expiration, P.name as principal_name"
            + " FROM domain D JOIN principal_group G ON D.domain_id = G.domain_id JOIN"
            + " principal_group_member M ON G.group_id = M.group_id JOIN principal P ON M.principal_id = P.principal_id"
            + " WHERE D.member_purge_expiry_days > -1 AND M.expiration is not null"
            + " AND (D.member_purge_expiry_days = 0 AND CURRENT_DATE() >= DATE_ADD(DATE(M.expiration), INTERVAL ? DAY)"
            + " OR D.member_purge_expiry_days != 0 AND CURRENT_DATE() >= DATE_ADD(DATE(M.expiration), INTERVAL D.member_purge_expiry_days DAY))"
            + " limit ? offset ?";
    private static final String SQL_INSERT_POLICY_TAG = "INSERT INTO policy_tags"
            + "(policy_id, policy_tags.key, policy_tags.value) VALUES (?,?,?);";
    private static final String SQL_POLICY_TAG_COUNT = "SELECT COUNT(*) FROM policy_tags WHERE policy_id=?";
    private static final String SQL_DELETE_POLICY_TAG = "DELETE FROM policy_tags WHERE policy_id=? AND policy_tags.key=?;";
    private static final String SQL_GET_POLICY_TAGS = "SELECT pt.key, pt.value FROM policy_tags pt WHERE pt.policy_id=?;";
    private static final String SQL_GET_DOMAIN_POLICY_TAGS = "SELECT p.name, pt.key, pt.value, p.version FROM policy_tags pt "
            + "JOIN policy p ON pt.policy_id = p.policy_id JOIN domain ON domain.domain_id=p.domain_id "
            + "WHERE domain.name=?";
    private static final String SQL_ROLE_EXPIRY_LAST_NOTIFIED_TIME = "SELECT last_notified_time FROM role_member"
            + " WHERE last_notified_time IS NOT NULL ORDER BY last_notified_time DESC LIMIT 1;";
    private static final String SQL_ROLE_REVIEW_LAST_NOTIFIED_TIME = "SELECT review_last_notified_time FROM role_member"
            + " WHERE review_last_notified_time IS NOT NULL ORDER BY review_last_notified_time DESC LIMIT 1;";
    private static final String SQL_GROUP_EXPIRY_LAST_NOTIFIED_TIME = "SELECT last_notified_time FROM principal_group_member"
            + " WHERE last_notified_time IS NOT NULL ORDER BY last_notified_time DESC LIMIT 1;";
    private static final String SQL_GET_ROLE_REVIEW_LIST  = "SELECT DISTINCT domain.name AS domain_name, role.name AS role_name,"
            + " role.member_expiry_days, role.service_expiry_days, role.group_expiry_days, role.member_review_days,"
            + " role.service_review_days, role.group_review_days, role.last_reviewed_time, role.created FROM role"
            + " JOIN domain ON role.domain_id=domain.domain_id JOIN role_member ON role.role_id=role_member.role_id WHERE role.trust='' AND"
            + " (role.member_expiry_days!=0 OR role.service_expiry_days!=0 OR role.group_expiry_days!=0 OR"
            + " role.member_review_days!=0 OR role.service_review_days!=0 OR role.group_review_days!=0) AND"
            + " role.domain_id IN (SELECT domain.domain_id FROM domain JOIN role ON role.domain_id=domain.domain_id"
            + " JOIN role_member ON role.role_id=role_member.role_id WHERE role_member.principal_id=? AND"
            + " role_member.active=true AND role.name='admin') ORDER BY domain.name, role.name;";
    private static final String SQL_GET_GROUP_REVIEW_LIST = "SELECT DISTINCT domain.name AS domain_name, principal_group.name AS group_name,"
            + " principal_group.member_expiry_days, principal_group.service_expiry_days, principal_group.last_reviewed_time,"
            + " principal_group.created FROM principal_group JOIN domain ON principal_group.domain_id=domain.domain_id"
            + " JOIN principal_group_member ON principal_group.group_id=principal_group_member.group_id WHERE"
            + " (principal_group.member_expiry_days!=0 OR principal_group.service_expiry_days!=0) AND"
            + " principal_group.domain_id IN (SELECT domain.domain_id FROM domain JOIN role ON"
            + " role.domain_id=domain.domain_id JOIN role_member ON role.role_id=role_member.role_id"
            + " WHERE role_member.principal_id=? AND role_member.active=true AND role.name='admin')"
            + " ORDER BY domain.name, principal_group.name;";
    private static final String SQL_INSERT_DOMAIN_CONTACT = "INSERT INTO domain_contacts (domain_id, type, name) VALUES (?,?,?);";
    private static final String SQL_UPDATE_DOMAIN_CONTACT = "UPDATE domain_contacts SET name=? WHERE domain_id=? and type=?;";
    private static final String SQL_DELETE_DOMAIN_CONTACT = "DELETE FROM domain_contacts WHERE domain_id=? AND type=?;";
    private static final String SQL_LIST_CONTACT_DOMAINS = "SELECT domain.name, domain_contacts.type FROM domain_contacts "
            + "JOIN domain ON domain_contacts.domain_id=domain.domain_id "
            + "WHERE domain_contacts.name=?;";
    private static final String SQL_LIST_DOMAIN_CONTACTS = "SELECT type, name FROM domain_contacts WHERE domain_id=?;";
    private static final String SQL_GET_LAST_ASSUME_ROLE_ASSERTION = "SELECT policy.modified FROM policy "
            + " JOIN assertion ON policy.policy_id=assertion.policy_id WHERE assertion.action='assume_role' "
            + " ORDER BY policy.modified DESC LIMIT 1";
    private static final String SQL_SET_DOMAIN_RESOURCE_OWNERSHIP = "UPDATE domain SET resource_owner=? WHERE name=?;";
    private static final String SQL_SET_ROLE_RESOURCE_OWNERSHIP = "UPDATE role SET resource_owner=? WHERE domain_id=? AND name=?;";
    private static final String SQL_SET_GROUP_RESOURCE_OWNERSHIP = "UPDATE principal_group SET resource_owner=? WHERE domain_id=? AND name=?;";
    private static final String SQL_SET_POLICY_RESOURCE_OWNERSHIP = "UPDATE policy SET resource_owner=? WHERE domain_id=? AND name=?;";
    private static final String SQL_SET_SERVICE_RESOURCE_OWNERSHIP = "UPDATE service SET resource_owner=? WHERE domain_id=? AND name=?;";

    private static final String CACHE_DOMAIN    = "d:";
    private static final String CACHE_ROLE      = "r:";
    private static final String CACHE_GROUP     = "g:";
    private static final String CACHE_POLICY    = "p:";
    private static final String CACHE_SERVICE   = "s:";
    private static final String CACHE_PRINCIPAL = "u:";
    private static final String CACHE_HOST      = "h:";
    private static final String ALL_PRINCIPALS  = "*";

    private static final String MYSQL_SERVER_TIMEZONE = System.getProperty(JDBCConsts.ZMS_PROP_MYSQL_SERVER_TIMEZONE, "GMT");

    private int roleTagsLimit = JDBCConsts.ZMS_DEFAULT_TAG_LIMIT;
    private int groupTagsLimit = JDBCConsts.ZMS_DEFAULT_TAG_LIMIT;
    private int domainTagsLimit = JDBCConsts.ZMS_DEFAULT_TAG_LIMIT;
    private int policyTagsLimit = JDBCConsts.ZMS_DEFAULT_TAG_LIMIT;
    private int serviceTagsLimit = JDBCConsts.ZMS_DEFAULT_TAG_LIMIT;

    Connection con;
    int queryTimeout = 60;
    Map objectMap;
    boolean transactionCompleted;
    DomainOptions domainOptions;
    private Object synchronizer = new Object();
    private volatile static Map> SERVER_TRUST_ROLES_MAP;
    private volatile static long SERVER_TRUST_ROLES_TIMESTAMP;
    private static final long SERVER_TRUST_ROLES_UPDATE_TIMEOUT = Long.parseLong(
            System.getProperty(JDBCConsts.ZMS_PROP_MYSQL_SERVER_TRUST_ROLES_UPDATE_TIMEOUT, "600000"));

    public JDBCConnection(Connection con, boolean autoCommit) throws SQLException {
        this.con = con;
        con.setAutoCommit(autoCommit);
        transactionCompleted = autoCommit;
        objectMap = new HashMap<>();
    }

    public void setObjectSynchronizer(Object synchronizer) {
        this.synchronizer = synchronizer;
    }

    /**
     * Used only by the test classes to reset the server trust roles map
     */
    void resetTrustRolesMap() {
        SERVER_TRUST_ROLES_MAP = null;
        SERVER_TRUST_ROLES_TIMESTAMP = 0;
    }

    @Override
    public void setDomainOptions(DomainOptions domainOptions) {
        this.domainOptions = domainOptions;
    }

    @Override
    public void setOperationTimeout(int queryTimeout) {
        this.queryTimeout = queryTimeout;
    }

    @Override
    public void setTagLimit(int domainLimit, int roleLimit, int groupLimit, int policyLimit, int serviceTagsLimit) {
        this.domainTagsLimit = domainLimit;
        this.roleTagsLimit = roleLimit;
        this.groupTagsLimit = groupLimit;
        this.policyTagsLimit = policyLimit;
        this.serviceTagsLimit = serviceTagsLimit;
    }

    @Override
    public void close() {

        if (con == null) {
            return;
        }

        // the client is always responsible for properly committing
        // all changes before closing the connection, but in case
        // we missed it, we're going to be safe and commit all
        // changes before closing the connection

        try {
            commitChanges();
        } catch (ServerResourceException ex) {
            // error is already logged, but we have to continue
            // processing so we can close our connection
        }

        try {
            con.close();
            con = null;
        } catch (SQLException ex) {
            LOG.error("close: state - {}, code - {}, message - {}", ex.getSQLState(),
                    ex.getErrorCode(), ex.getMessage());
        }
    }

    @Override
    public void rollbackChanges() {

        if (LOG.isDebugEnabled()) {
            LOG.debug("rollback transaction changes...");
        }

        if (transactionCompleted) {
            return;
        }

        try {
            con.rollback();
        } catch (SQLException ex) {
            LOG.error("rollbackChanges: state - {}, code - {}, message - {}", ex.getSQLState(),
                    ex.getErrorCode(), ex.getMessage());
        }

        transactionCompleted = true;
        try {
            con.setAutoCommit(true);
        } catch (SQLException ex) {
            LOG.error("rollback auto-commit after failure: state - {}, code - {}, message - {}",
                    ex.getSQLState(), ex.getErrorCode(), ex.getMessage());
        }
    }

    @Override
    public void commitChanges() throws ServerResourceException {

        final String caller = "commitChanges";
        if (transactionCompleted) {
            return;
        }

        try {
            con.commit();
            transactionCompleted = true;
            con.setAutoCommit(true);
        } catch (SQLException ex) {
            LOG.error("commitChanges: state - {}, code - {}, message - {}", ex.getSQLState(),
                    ex.getErrorCode(), ex.getMessage());
            transactionCompleted = true;
            throw sqlError(ex, caller);
        }
    }

    int executeUpdate(PreparedStatement ps, String caller) throws SQLException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("{}: {}", caller, ps.toString());
        }
        ps.setQueryTimeout(queryTimeout);
        return ps.executeUpdate();
    }

    ResultSet executeQuery(PreparedStatement ps, String caller) throws SQLException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("{}: {}", caller, ps.toString());
        }
        ps.setQueryTimeout(queryTimeout);
        return ps.executeQuery();
    }

    int[] executeBatch(PreparedStatement ps, String caller) throws SQLException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("{}: {}", caller, ps.toString());
        }
        ps.setQueryTimeout(queryTimeout);
        return ps.executeBatch();
    }

    Domain saveDomainSettings(String domainName, ResultSet rs, boolean fetchAddlDetails) throws SQLException {
        Domain domain = new Domain().setName(domainName)
                .setAuditEnabled(rs.getBoolean(JDBCConsts.DB_COLUMN_AUDIT_ENABLED))
                .setEnabled(rs.getBoolean(JDBCConsts.DB_COLUMN_ENABLED))
                .setModified(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime()))
                .setDescription(saveValue(rs.getString(JDBCConsts.DB_COLUMN_DESCRIPTION)))
                .setOrg(saveValue(rs.getString(JDBCConsts.DB_COLUMN_ORG)))
                .setId(saveUuidValue(rs.getString(JDBCConsts.DB_COLUMN_UUID)))
                .setAccount(saveValue(rs.getString(JDBCConsts.DB_COLUMN_ACCOUNT)))
                .setAzureSubscription(saveValue(rs.getString(JDBCConsts.DB_COLUMN_AZURE_SUBSCRIPTION)))
                .setAzureTenant(saveValue(rs.getString(JDBCConsts.DB_COLUMN_AZURE_TENANT)))
                .setAzureClient(saveValue(rs.getString(JDBCConsts.DB_COLUMN_AZURE_CLIENT)))
                .setGcpProject(saveValue(rs.getString(JDBCConsts.DB_COLUMN_GCP_PROJECT_ID)))
                .setGcpProjectNumber(saveValue(rs.getString(JDBCConsts.DB_COLUMN_GCP_PROJECT_NUMBER)))
                .setYpmId(rs.getInt(JDBCConsts.DB_COLUMN_YPM_ID))
                .setProductId(saveValue(rs.getString(JDBCConsts.DB_COLUMN_PRODUCT_ID)))
                .setCertDnsDomain(saveValue(rs.getString(JDBCConsts.DB_COLUMN_CERT_DNS_DOMAIN)))
                .setMemberExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_EXPIRY_DAYS), 0))
                .setTokenExpiryMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_TOKEN_EXPIRY_MINS), 0))
                .setRoleCertExpiryMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_ROLE_CERT_EXPIRY_MINS), 0))
                .setServiceCertExpiryMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_CERT_EXPIRY_MINS), 0))
                .setApplicationId(saveValue(rs.getString(JDBCConsts.DB_COLUMN_APPLICATION_ID)))
                .setSignAlgorithm(saveValue(rs.getString(JDBCConsts.DB_COLUMN_SIGN_ALGORITHM)))
                .setServiceExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_EXPIRY_DAYS), 0))
                .setGroupExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_GROUP_EXPIRY_DAYS), 0))
                .setUserAuthorityFilter(saveValue(rs.getString(JDBCConsts.DB_COLUMN_USER_AUTHORITY_FILTER)))
                .setBusinessService(saveValue(rs.getString(JDBCConsts.DB_COLUMN_BUSINESS_SERVICE)))
                .setMemberPurgeExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_PURGE_EXPIRY_DAYS), 0))
                .setFeatureFlags(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_FEATURE_FLAGS), 0))
                .setEnvironment(saveValue(rs.getString(JDBCConsts.DB_COLUMN_ENVIRONMENT)))
                .setResourceOwnership(ResourceOwnership.getResourceDomainOwnership(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE_OWNER)))
                .setX509CertSignerKeyId(saveValue(rs.getString(JDBCConsts.DB_COLUMN_X509_CERT_SIGNER_KEYID)))
                .setSshCertSignerKeyId(saveValue(rs.getString(JDBCConsts.DB_COLUMN_SSH_CERT_SIGNER_KEYID)))
                .setSlackChannel(saveValue(rs.getString(JDBCConsts.DB_COLUMN_SLACK_CHANNEL)));
        if (fetchAddlDetails) {
            int domainId = rs.getInt(JDBCConsts.DB_COLUMN_DOMAIN_ID);
            domain.setTags(getDomainTags(domainId));
            domain.setContacts(getDomainContacts(domainId));
        }
        return domain;
    }

    @Override
    public Domain getDomain(String domainName) throws ServerResourceException {

        final String caller = "getDomain";
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return saveDomainSettings(domainName, rs, true);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    @Override
    public boolean insertDomain(Domain domain) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertDomain";

        // we need to verify that our account and product ids are unique
        // in the store. we can't rely on db uniqueness check since
        // some of the domains will not have these attributes set

        verifyDomainAwsAccountUniqueness(domain.getName(), domain.getAccount(), caller);
        verifyDomainAzureSubscriptionUniqueness(domain.getName(), domain.getAzureSubscription(), caller);
        verifyDomainGcpProjectUniqueness(domain.getName(), domain.getGcpProject(), caller);
        verifyDomainProductIdUniqueness(domain.getName(), domain.getYpmId(), caller);
        verifyDomainProductIdUniqueness(domain.getName(), domain.getProductId(), caller);
        verifyDomainNameDashUniqueness(domain.getName(), caller);

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN)) {
            ps.setString(1, domain.getName());
            ps.setString(2, processInsertValue(domain.getDescription()));
            ps.setString(3, processInsertValue(domain.getOrg()));
            ps.setString(4, processInsertUuidValue(domain.getId()));
            ps.setBoolean(5, processInsertValue(domain.getEnabled(), true));
            ps.setBoolean(6, processInsertValue(domain.getAuditEnabled(), false));
            ps.setString(7, processInsertValue(domain.getAccount()));
            ps.setInt(8, processInsertValue(domain.getYpmId()));
            ps.setString(9, processInsertValue(domain.getApplicationId()));
            ps.setString(10, processInsertValue(domain.getCertDnsDomain()));
            ps.setInt(11, processInsertValue(domain.getMemberExpiryDays()));
            ps.setInt(12, processInsertValue(domain.getTokenExpiryMins()));
            ps.setInt(13, processInsertValue(domain.getServiceCertExpiryMins()));
            ps.setInt(14, processInsertValue(domain.getRoleCertExpiryMins()));
            ps.setString(15, processInsertValue(domain.getSignAlgorithm()));
            ps.setInt(16, processInsertValue(domain.getServiceExpiryDays()));
            ps.setString(17, processInsertValue(domain.getUserAuthorityFilter()));
            ps.setInt(18, processInsertValue(domain.getGroupExpiryDays()));
            ps.setString(19, processInsertValue(domain.getAzureSubscription()));
            ps.setString(20, processInsertValue(domain.getBusinessService()));
            ps.setInt(21, processInsertValue(domain.getMemberPurgeExpiryDays()));
            ps.setString(22, processInsertValue(domain.getGcpProject()));
            ps.setString(23, processInsertValue(domain.getGcpProjectNumber()));
            ps.setString(24, processInsertValue(domain.getProductId()));
            ps.setInt(25, processInsertValue(domain.getFeatureFlags()));
            ps.setString(26, processInsertValue(domain.getEnvironment()));
            ps.setString(27, processInsertValue(domain.getAzureTenant()));
            ps.setString(28, processInsertValue(domain.getAzureClient()));
            ps.setString(29, processInsertValue(domain.getX509CertSignerKeyId()));
            ps.setString(30, processInsertValue(domain.getSshCertSignerKeyId()));
            ps.setString(31, processInsertValue(domain.getSlackChannel()));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    void verifyDomainNameDashUniqueness(final String name, String caller) throws ServerResourceException {

        // with our certificates we replace .'s with -'s
        // so we need to make sure we don't allow creation
        // of domains such as sports.api and sports-api since
        // they'll have the same component value

        final String domainMatch = name.replace('.', '-');
        final String domainQuery = name.replace('.', '_').replace('-', '_');

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAINS_WITH_NAME)) {
            ps.setString(1, domainQuery);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String domainName = rs.getString(1);
                    if (domainMatch.equals(domainName.replace('.', '-'))) {
                        throw requestError(caller, "Domain name conflict: " + domainName);
                    }
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    void verifyDomainProductIdUniqueness(final String name, Integer productNumber, final String caller) throws ServerResourceException {

        if (productNumber == null || productNumber == 0) {
            return;
        }
        if (domainOptions != null && !domainOptions.getEnforceUniqueProductIds()) {
            return;
        }
        final String domainName = lookupDomainByProductId(productNumber);
        if (domainName != null && !domainName.equals(name)) {
            throw requestError(caller, "Product Id: " + productNumber + " is already assigned to domain: " + domainName);
        }
    }

    void verifyDomainProductIdUniqueness(final String name, String productId, final String caller) throws ServerResourceException {

        if (StringUtil.isEmpty(productId)) {
            return;
        }
        if (domainOptions != null && !domainOptions.getEnforceUniqueProductIds()) {
            return;
        }
        final String domainName = lookupDomainByProductId(productId);
        if (domainName != null && !domainName.equals(name)) {
            throw requestError(caller, "Product Id: " + productId + " is already assigned to domain: " + domainName);
        }
    }

    void verifyDomainAwsAccountUniqueness(final String name, final String account, final String caller) throws ServerResourceException {

        if (StringUtil.isEmpty(account)) {
            return;
        }
        if (domainOptions != null && !domainOptions.getEnforceUniqueAWSAccounts()) {
            return;
        }
        final String domainName = lookupDomainByCloudProvider(ObjectStoreConnection.PROVIDER_AWS, account);
        if (domainName != null && !domainName.equals(name)) {
            throw requestError(caller, "Account Id: " + account + " is already assigned to domain: " + domainName);
        }
    }

    void verifyDomainAzureSubscriptionUniqueness(final String name, final String subscription, final String caller) throws ServerResourceException {

        if (StringUtil.isEmpty(subscription)) {
            return;
        }
        if (domainOptions != null && !domainOptions.getEnforceUniqueAzureSubscriptions()) {
            return;
        }
        final String domainName = lookupDomainByCloudProvider(ObjectStoreConnection.PROVIDER_AZURE, subscription);
        if (domainName != null && !domainName.equals(name)) {
            throw requestError(caller, "Subscription Id: " + subscription + " is already assigned to domain: " + domainName);
        }
    }

    void verifyDomainGcpProjectUniqueness(final String name, final String project, final String caller) throws ServerResourceException {

        if (StringUtil.isEmpty(project)) {
            return;
        }
        if (domainOptions != null && !domainOptions.getEnforceUniqueGCPProjects()) {
            return;
        }
        final String domainName = lookupDomainByCloudProvider(ObjectStoreConnection.PROVIDER_GCP, project);
        if (domainName != null && !domainName.equals(name)) {
            throw requestError(caller, "Project: " + project + " is already assigned to domain: " + domainName);
        }
    }

    @Override
    public boolean updateDomain(Domain domain) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateDomain";

        // we need to verify that our account and product ids are unique
        // in the store. we can't rely on db uniqueness check since
        // some of the domains will not have these attributes set

        verifyDomainAwsAccountUniqueness(domain.getName(), domain.getAccount(), caller);
        verifyDomainAzureSubscriptionUniqueness(domain.getName(), domain.getAzureSubscription(), caller);
        verifyDomainGcpProjectUniqueness(domain.getName(), domain.getGcpProject(), caller);
        verifyDomainProductIdUniqueness(domain.getName(), domain.getYpmId(), caller);
        verifyDomainProductIdUniqueness(domain.getName(), domain.getProductId(), caller);

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_DOMAIN)) {
            ps.setString(1, processInsertValue(domain.getDescription()));
            ps.setString(2, processInsertValue(domain.getOrg()));
            ps.setString(3, processInsertUuidValue(domain.getId()));
            ps.setBoolean(4, processInsertValue(domain.getEnabled(), true));
            ps.setBoolean(5, processInsertValue(domain.getAuditEnabled(), false));
            ps.setString(6, processInsertValue(domain.getAccount()));
            ps.setInt(7, processInsertValue(domain.getYpmId()));
            ps.setString(8, processInsertValue(domain.getApplicationId()));
            ps.setString(9, processInsertValue(domain.getCertDnsDomain()));
            ps.setInt(10, processInsertValue(domain.getMemberExpiryDays()));
            ps.setInt(11, processInsertValue(domain.getTokenExpiryMins()));
            ps.setInt(12, processInsertValue(domain.getServiceCertExpiryMins()));
            ps.setInt(13, processInsertValue(domain.getRoleCertExpiryMins()));
            ps.setString(14, processInsertValue(domain.getSignAlgorithm()));
            ps.setInt(15, processInsertValue(domain.getServiceExpiryDays()));
            ps.setString(16, processInsertValue(domain.getUserAuthorityFilter()));
            ps.setInt(17, processInsertValue(domain.getGroupExpiryDays()));
            ps.setString(18, processInsertValue(domain.getAzureSubscription()));
            ps.setString(19, processInsertValue(domain.getBusinessService()));
            ps.setInt(20, processInsertValue(domain.getMemberPurgeExpiryDays()));
            ps.setString(21, processInsertValue(domain.getGcpProject()));
            ps.setString(22, processInsertValue(domain.getGcpProjectNumber()));
            ps.setString(23, processInsertValue(domain.getProductId()));
            ps.setInt(24, processInsertValue(domain.getFeatureFlags()));
            ps.setString(25, processInsertValue(domain.getEnvironment()));
            ps.setString(26, processInsertValue(domain.getAzureTenant()));
            ps.setString(27, processInsertValue(domain.getAzureClient()));
            ps.setString(28, processInsertValue(domain.getX509CertSignerKeyId()));
            ps.setString(29, processInsertValue(domain.getSshCertSignerKeyId()));
            ps.setString(30, processInsertValue(domain.getSlackChannel()));
            ps.setString(31, domain.getName());
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // invalidate the cache domain entry

        objectMap.remove(CACHE_DOMAIN + domain.getName());
        return (affectedRows > 0);
    }

    @Override
    public boolean updateDomainModTimestamp(String domainName) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateDomainModTimestamp";

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_DOMAIN_MOD_TIMESTAMP)) {
            ps.setString(1, domainName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public long getDomainModTimestamp(String domainName) throws ServerResourceException {

        long modTime = 0;
        final String caller = "getDomainModTimestamp";

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_MOD_TIMESTAMP)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    modTime = rs.getTimestamp(1).getTime();
                }
            }
        } catch (SQLException ex) {
            // ignore any failures and return default value 0
        }
        return modTime;
    }

    @Override
    public boolean deleteDomain(String domainName) throws ServerResourceException {

        int affectedRows;
        final String caller = "deleteDomain";

        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN)) {
            ps.setString(1, domainName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    PreparedStatement prepareDomainScanStatement(String prefix, long modifiedSince)
            throws SQLException {

        PreparedStatement ps;
        if (!StringUtil.isEmpty(prefix)) {
            int len = prefix.length();
            char c = (char) (prefix.charAt(len - 1) + 1);
            String stop = prefix.substring(0, len - 1) + c;
            if (modifiedSince != 0) {
                ps = con.prepareStatement(SQL_LIST_DOMAIN_PREFIX_MODIFIED);
                ps.setString(1, prefix);
                ps.setString(2, stop);
                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(MYSQL_SERVER_TIMEZONE));
                ps.setTimestamp(3, new java.sql.Timestamp(modifiedSince), cal);
            } else {
                ps = con.prepareStatement(SQL_LIST_DOMAIN_PREFIX);
                ps.setString(1, prefix);
                ps.setString(2, stop);
            }
        } else if (modifiedSince != 0) {
            ps = con.prepareStatement(SQL_LIST_DOMAIN_MODIFIED);
            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(MYSQL_SERVER_TIMEZONE));
            ps.setTimestamp(1, new java.sql.Timestamp(modifiedSince), cal);
        } else {
            ps = con.prepareStatement(SQL_LIST_DOMAIN);
        }
        return ps;
    }

    PreparedStatement prepareScanByRoleStatement(String roleMember, String roleName)
            throws SQLException {

        PreparedStatement ps;
        boolean memberPresent = (roleMember != null && !roleMember.isEmpty());
        boolean rolePresent = (roleName != null && !roleName.isEmpty());
        if (memberPresent && rolePresent) {
            ps = con.prepareStatement(SQL_LIST_DOMAIN_ROLE_NAME_MEMBER);
            ps.setString(1, roleMember);
            ps.setString(2, roleName);
        } else if (memberPresent) {
            ps = con.prepareStatement(SQL_LIST_DOMAIN_ROLE_MEMBER);
            ps.setString(1, roleMember);
        } else if (rolePresent) {
            ps = con.prepareStatement(SQL_LIST_DOMAIN_ROLE_NAME);
            ps.setString(1, roleName);
        } else {
            ps = con.prepareStatement(SQL_LIST_DOMAIN);
        }
        return ps;
    }

    @Override
    public List lookupDomainByRole(String roleMember, String roleName) throws ServerResourceException {

        final String caller = "lookupDomainByRole";

        // it's possible that we'll get duplicate domain names returned
        // from this result - e.g. when no role name is filtered on so
        // we're going to automatically skip those by using a set

        Set uniqueDomains = new HashSet<>();
        try (PreparedStatement ps = prepareScanByRoleStatement(roleMember, roleName)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    uniqueDomains.add(rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        List domains = new ArrayList<>(uniqueDomains);
        Collections.sort(domains);
        return domains;
    }

    @Override
    public List lookupDomainByBusinessService(String businessService) throws ServerResourceException {

        final String caller = "lookupDomainByBusinessService";

        List domains = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_WITH_BUSINESS_SERVICE)) {
            ps.setString(1, businessService.trim());
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    domains.add(rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return domains;
    }

    @Override
    public String lookupDomainByProductId(int productId) throws ServerResourceException {

        final String caller = "lookupDomainByProductId";
        String domainName = null;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_WITH_YPM_ID)) {
            ps.setInt(1, productId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    domainName = rs.getString(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domainName;
    }

    @Override
    public String lookupDomainByProductId(String productId) throws ServerResourceException {

        final String caller = "lookupDomainByProductId";
        String domainName = null;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_WITH_PRODUCT_ID)) {
            ps.setString(1, productId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    domainName = rs.getString(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domainName;
    }

    String getCloudProviderLookupDomainSQLCommand(final String provider) {
        if (provider == null) {
            return null;
        }
        switch (provider.toLowerCase()) {
            case ObjectStoreConnection.PROVIDER_AWS:
                return SQL_GET_DOMAIN_WITH_AWS_ACCOUNT;
            case ObjectStoreConnection.PROVIDER_AZURE:
                return SQL_GET_DOMAIN_WITH_AZURE_SUBSCRIPTION;
            case ObjectStoreConnection.PROVIDER_GCP:
                return SQL_GET_DOMAIN_WITH_GCP_PROJECT;
        }
        return null;
    }

    String getCloudProviderListDomainsSQLCommand(final String provider) {
        if (provider == null) {
            return null;
        }
        switch (provider.toLowerCase()) {
            case ObjectStoreConnection.PROVIDER_AWS:
                return SQL_LIST_DOMAINS_WITH_AWS_ACCOUNT;
            case ObjectStoreConnection.PROVIDER_AZURE:
                return SQL_LIST_DOMAINS_WITH_AZURE_SUBSCRIPTION;
            case ObjectStoreConnection.PROVIDER_GCP:
                return SQL_LIST_DOMAINS_WITH_GCP_PROJECT;
        }
        return null;
    }

    String getCloudProviderColumnName(final String provider) {
        if (provider == null) {
            return null;
        }
        switch (provider.toLowerCase()) {
            case ObjectStoreConnection.PROVIDER_AWS:
                return JDBCConsts.DB_COLUMN_ACCOUNT;
            case ObjectStoreConnection.PROVIDER_AZURE:
                return JDBCConsts.DB_COLUMN_AZURE_SUBSCRIPTION;
            case ObjectStoreConnection.PROVIDER_GCP:
                return JDBCConsts.DB_COLUMN_GCP_PROJECT_ID;
        }
        return null;
    }

    @Override
    public String lookupDomainByCloudProvider(String provider, String value) throws ServerResourceException {

        final String caller = "lookupDomainByCloudProvider";
        final String sqlCmd = getCloudProviderLookupDomainSQLCommand(provider);
        if (sqlCmd == null || value == null) {
            return null;
        }
        String domainName = null;
        try (PreparedStatement ps = con.prepareStatement(sqlCmd)) {
            ps.setString(1, value.trim());
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    domainName = rs.getString(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domainName;
    }

    @Override
    public Map listDomainsByCloudProvider(String provider) throws ServerResourceException {

        final String caller = "listDomainByCloudProvider";
        final String sqlCmd = getCloudProviderListDomainsSQLCommand(provider);
        if (sqlCmd == null) {
            return null;
        }
        final String columnName = getCloudProviderColumnName(provider);
        Map domains = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(sqlCmd)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    domains.put(rs.getString(JDBCConsts.DB_COLUMN_NAME), rs.getString(columnName));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domains;
    }

    @Override
    public List listDomains(String prefix, long modifiedSince) throws ServerResourceException {

        final String caller = "listDomains";

        List domains = new ArrayList<>();
        try (PreparedStatement ps = prepareDomainScanStatement(prefix, modifiedSince)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    domains.add(rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(domains);
        return domains;
    }

    public boolean deleteDomainTags(String domainName, Set tagsToRemove) throws ServerResourceException {
        final String caller = "deleteDomainTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        boolean res = true;
        for (String tagKey : tagsToRemove) {
            try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN_TAG)) {
                ps.setInt(1, domainId);
                ps.setString(2, processInsertValue(tagKey));
                res &= (executeUpdate(ps, caller) > 0);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
        return res;
    }

    public boolean insertDomainTags(String domainName, Map tags) throws ServerResourceException {
        final String caller = "updateDomainTags";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int curTagCount = getDomainTagsCount(domainId);
        int newTagCount = calculateTagCount(tags);
        if (curTagCount + newTagCount > domainTagsLimit) {
            throw requestError(caller, "domain tag quota exceeded - limit: "
                + domainTagsLimit + ", current tags count: " + curTagCount + ", new tags count: " + newTagCount);
        }

        boolean res = true;
        for (Map.Entry e : tags.entrySet()) {
            for (String tagValue : e.getValue().getList()) {
                try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN_TAG)) {
                    ps.setInt(1, domainId);
                    ps.setString(2, processInsertValue(e.getKey()));
                    ps.setString(3, processInsertValue(tagValue));
                    res &= (executeUpdate(ps, caller) > 0);
                } catch (SQLException ex) {
                    throw sqlError(ex, caller);
                }
            }
        }

        return res;
    }

    private int calculateTagCount(Map tags) {
        int count = 0;
        for (Map.Entry e : tags.entrySet()) {
            count += e.getValue().getList().size();
        }
        return count;
    }

    int getDomainTagsCount(int domainId) throws ServerResourceException {
        final String caller = "getDomainTagsCount";
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_DOMAIN_TAG_COUNT)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    public Map getDomainTags(int domainId) throws SQLException {
        final String caller = "getDomainTags";
        Map domainTag = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_TAGS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String tagKey = rs.getString(1);
                    String tagValue = rs.getString(2);
                    if (domainTag == null) {
                        domainTag = new HashMap<>();
                    }
                    TagValueList tagValues = domainTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        }
        return domainTag;
    }

    public List lookupDomainByTags(String tagKey, String tagValue) throws ServerResourceException {
        final String caller = "lookupDomainByTags";

        // since domain tag might include multiple values - duplicates
        // are possible. use Set to avoid duplicates

        Set uniqueDomains = new HashSet<>();

        try (PreparedStatement ps = prepareScanByTags(tagKey, tagValue)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    uniqueDomains.add(rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        List domains = new ArrayList<>(uniqueDomains);
        Collections.sort(domains);
        return domains;
    }

    PreparedStatement prepareScanByTags(String tagKey, String tagValue) throws SQLException {
        PreparedStatement ps;
        if (!StringUtil.isEmpty(tagValue)) {
            ps = con.prepareStatement(SQL_LOOKUP_DOMAIN_BY_TAG_KEY_VAL);
            ps.setString(1, tagKey);
            ps.setString(2, tagValue);
        } else {
            ps = con.prepareStatement(SQL_LOOKUP_DOMAIN_BY_TAG_KEY);
            ps.setString(1, tagKey);
        }
        return ps;
    }

    @Override
    public boolean insertDomainTemplate(String domainName, String templateName, String params) throws ServerResourceException {

        final String caller = "insertDomainTemplate";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN_TEMPLATE)) {
            ps.setInt(1, domainId);
            ps.setString(2, templateName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateDomainTemplate(String domainName, String templateName, TemplateMetaData templateMetaData) throws ServerResourceException {

        final String caller = "updateDomainTemplate";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_DOMAIN_TEMPLATE)) {
            ps.setInt(1, templateMetaData.getLatestVersion());
            ps.setInt(2, domainId);
            ps.setString(3, templateName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }


    @Override
    public boolean deleteDomainTemplate(String domainName, String templateName, String params) throws ServerResourceException {

        final String caller = "deleteDomainTemplate";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN_TEMPLATE)) {
            ps.setInt(1, domainId);
            ps.setString(2, templateName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public List listDomainTemplates(String domainName) throws ServerResourceException {

        final String caller = "listDomainTemplates";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        List templates = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_TEMPLATE)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    templates.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(templates);
        return templates;
    }

    @Override
    public Map> getDomainFromTemplateName(Map templateNameAndLatestVersion) throws ServerResourceException {
        final String caller = "getDomainsFromTemplate";
        Map> domainNameTemplateListMap = new HashMap<>();

        try (PreparedStatement ps = con.prepareStatement(generateDomainTemplateVersionQuery(templateNameAndLatestVersion))) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String domainName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    String templateName = rs.getString(JDBCConsts.DB_COLUMN_TEMPLATE_NAME);
                    if (domainNameTemplateListMap.get(domainName) != null) {
                        List tempTemplateList = domainNameTemplateListMap.get(domainName);
                        tempTemplateList.add(templateName);
                    } else {
                        List templateList = new ArrayList<>();
                        templateList.add(templateName);
                        domainNameTemplateListMap.put(domainName, templateList);
                    }
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domainNameTemplateListMap;
    }

    int getDomainId(String domainName) {
        return getDomainId(domainName, false);
    }

    int getDomainId(String domainName, boolean domainStateCheck) {

        final String caller = "getDomainId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = CACHE_DOMAIN + domainName;
        Integer value = objectMap.get(cacheKey);
        if (value != null) {
            return value;
        }

        int domainId = 0;
        final String sqlCommand = domainStateCheck ? SQL_GET_ACTIVE_DOMAIN_ID : SQL_GET_DOMAIN_ID;
        try (PreparedStatement ps = con.prepareStatement(sqlCommand)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    domainId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get domain id for name: {} error code: {} msg: {}",
                    domainName, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (domainId != 0) {
            objectMap.put(cacheKey, domainId);
        }

        return domainId;
    }

    int getPolicyId(int domainId, String policyName, String version) {

        final String caller = "getPolicyId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = StringUtil.isEmpty(version) ? null : CACHE_POLICY + domainId + '.' + policyName + '.' + version;

        if (cacheKey != null) {
            Integer value = objectMap.get(cacheKey);
            if (value != null) {
                return value;
            }
        }

        int policyId = 0;
        try (PreparedStatement ps = con.prepareStatement(StringUtil.isEmpty(version) ? SQL_GET_ACTIVE_POLICY_ID : SQL_GET_POLICY_VERSION_ID)) {
            ps.setInt(1, domainId);
            ps.setString(2, policyName);
            if (!StringUtil.isEmpty(version)) {
                ps.setString(3, version);
            }
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    policyId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get policy id for name: {} error code: {} msg: {}",
                    policyName, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (policyId != 0 && cacheKey != null) {
            objectMap.put(cacheKey, policyId);
        }

        return policyId;
    }

    int getRoleId(int domainId, String roleName) {

        final String caller = "getRoleId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = CACHE_ROLE + domainId + '.' + roleName;

        Integer value = objectMap.get(cacheKey);
        if (value != null) {
            return value;
        }

        int roleId = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE_ID)) {
            ps.setInt(1, domainId);
            ps.setString(2, roleName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    roleId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get role id for name: {} error code: {} msg: {}",
                    roleName, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (roleId != 0) {
            objectMap.put(cacheKey, roleId);
        }

        return roleId;
    }

    int getGroupId(int domainId, final String groupName) {

        final String caller = "getGroupId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = CACHE_GROUP + domainId + '.' + groupName;

        Integer value = objectMap.get(cacheKey);
        if (value != null) {
            return value;
        }

        int groupId = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_GROUP_ID)) {
            ps.setInt(1, domainId);
            ps.setString(2, groupName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    groupId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get group id for name: {} error code: {} msg: {}",
                    groupName, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (groupId != 0) {
            objectMap.put(cacheKey, groupId);
        }

        return groupId;
    }

    int getServiceId(int domainId, String serviceName) {

        final String caller = "getServiceId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = CACHE_SERVICE + domainId + '.' + serviceName;

        Integer value = objectMap.get(cacheKey);
        if (value != null) {
            return value;
        }

        int serviceId = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_SERVICE_ID)) {
            ps.setInt(1, domainId);
            ps.setString(2, serviceName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    serviceId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get service id for name: {} error code: {} msg: {}",
                    serviceName, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (serviceId != 0) {
            objectMap.put(cacheKey, serviceId);
        }

        return serviceId;
    }

    int getPrincipalId(String principal) {

        final String caller = "getPrincipalId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = CACHE_PRINCIPAL + principal;
        Integer value = objectMap.get(cacheKey);
        if (value != null) {
            return value;
        }

        int principalId = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL_ID)) {
            ps.setString(1, principal);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    principalId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get principal id for name: {} error code: {} msg: {}",
                    principal, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (principalId != 0) {
            objectMap.put(cacheKey, principalId);
        }

        return principalId;
    }

    int getHostId(String hostName) {

        final String caller = "getHostId";

        // first check to see if our cache contains this value
        // otherwise we'll contact the MySQL Server

        final String cacheKey = CACHE_HOST + hostName;
        Integer value = objectMap.get(cacheKey);
        if (value != null) {
            return value;
        }

        int hostId = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_HOST_ID)) {
            ps.setString(1, hostName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    hostId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get host id for name: {} error code: {} msg: {}",
                    hostName, ex.getErrorCode(), ex.getMessage());
        }

        // before returning the value update our cache

        if (hostId != 0) {
            objectMap.put(cacheKey, hostId);
        }

        return hostId;
    }

    int getLastInsertId() {

        int lastInsertId = 0;
        final String caller = "getLastInsertId";

        try (PreparedStatement ps = con.prepareStatement(SQL_LAST_INSERT_ID)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    lastInsertId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to get last insert id - error code: {} msg: {}",
                    ex.getErrorCode(), ex.getMessage());
        }
        return lastInsertId;
    }

    PreparedStatement preparePrincipalScanStatement(String domainName)
            throws SQLException {

        PreparedStatement ps;
        if (!StringUtil.isEmpty(domainName)) {
            ps = con.prepareStatement(SQL_LIST_PRINCIPAL_DOMAIN);
            ps.setString(1, domainName + ".%");
            ps.setString(2, domainName + ":group.%");
        } else {
            ps = con.prepareStatement(SQL_LIST_PRINCIPAL);
        }
        return ps;
    }

    @Override
    public List listPrincipals(String domainName) throws ServerResourceException {

        final String caller = "listPrincipals";

        List principals = new ArrayList<>();
        try (PreparedStatement ps = preparePrincipalScanStatement(domainName)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    principals.add(rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return principals;
    }

    @Override
    public boolean deletePrincipal(String principalName, boolean subDomains) throws ServerResourceException {

        final String caller = "deletePrincipal";

        // first we're going to delete the principal from the principal table

        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_PRINCIPAL)) {
            ps.setString(1, principalName);
            executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // next delete any principal that was created in the principal's
        // sub-domains. These will be in the format "principal.%"

        if (subDomains) {
            final String domainPattern = principalName + ".%";
            try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_SUB_PRINCIPALS)) {
                ps.setString(1, domainPattern);
                executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }

        return true;
    }

    @Override
    public Role getRole(String domainName, String roleName) throws ServerResourceException {

        final String caller = "getRole";

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE)) {
            ps.setString(1, domainName);
            ps.setString(2, roleName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return retrieveRole(rs, domainName, roleName);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    @Override
    public boolean insertRole(String domainName, Role role) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertRole";

        String roleName = Utils.extractRoleName(domainName, role.getName());
        if (roleName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " insert role name: " + role.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        java.sql.Timestamp lastReviewedTime = role.getLastReviewedDate() == null ? null :
                new java.sql.Timestamp(role.getLastReviewedDate().millis());

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE)) {
            ps.setString(1, roleName);
            ps.setInt(2, domainId);
            ps.setString(3, processInsertValue(role.getTrust()));
            ps.setBoolean(4, processInsertValue(role.getAuditEnabled(), false));
            ps.setBoolean(5, processInsertValue(role.getSelfServe(), false));
            ps.setInt(6, processInsertValue(role.getMemberExpiryDays()));
            ps.setInt(7, processInsertValue(role.getTokenExpiryMins()));
            ps.setInt(8, processInsertValue(role.getCertExpiryMins()));
            ps.setString(9, processInsertValue(role.getSignAlgorithm()));
            ps.setInt(10, processInsertValue(role.getServiceExpiryDays()));
            ps.setInt(11, processInsertValue(role.getMemberReviewDays()));
            ps.setInt(12, processInsertValue(role.getServiceReviewDays()));
            ps.setInt(13, processInsertValue(role.getGroupReviewDays()));
            ps.setBoolean(14, processInsertValue(role.getReviewEnabled(), false));
            ps.setString(15, processInsertValue(role.getNotifyRoles()));
            ps.setString(16, processInsertValue(role.getUserAuthorityFilter()));
            ps.setString(17, processInsertValue(role.getUserAuthorityExpiration()));
            ps.setString(18, processInsertValue(role.getDescription()));
            ps.setInt(19, processInsertValue(role.getGroupExpiryDays()));
            ps.setBoolean(20, processInsertValue(role.getDeleteProtection(), false));
            ps.setTimestamp(21, lastReviewedTime);
            ps.setInt(22, processInsertValue(role.getMaxMembers()));
            ps.setBoolean(23, processInsertValue(role.getSelfRenew(), false));
            ps.setInt(24, processInsertValue(role.getSelfRenewMins()));
            ps.setString(25, processInsertValue(role.getPrincipalDomainFilter()));
            ps.setString(26, processInsertValue(role.getNotifyDetails()));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateRole(String domainName, Role role) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateRole";

        String roleName = Utils.extractRoleName(domainName, role.getName());
        if (roleName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " update role name: " + role.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }

        java.sql.Timestamp lastReviewedTime = role.getLastReviewedDate() == null ? null :
                new java.sql.Timestamp(role.getLastReviewedDate().millis());

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE)) {
            ps.setString(1, processInsertValue(role.getTrust()));
            ps.setBoolean(2, processInsertValue(role.getAuditEnabled(), false));
            ps.setBoolean(3, processInsertValue(role.getSelfServe(), false));
            ps.setInt(4, processInsertValue(role.getMemberExpiryDays()));
            ps.setInt(5, processInsertValue(role.getTokenExpiryMins()));
            ps.setInt(6, processInsertValue(role.getCertExpiryMins()));
            ps.setString(7, processInsertValue(role.getSignAlgorithm()));
            ps.setInt(8, processInsertValue(role.getServiceExpiryDays()));
            ps.setInt(9, processInsertValue(role.getMemberReviewDays()));
            ps.setInt(10, processInsertValue(role.getServiceReviewDays()));
            ps.setInt(11, processInsertValue(role.getGroupReviewDays()));
            ps.setBoolean(12, processInsertValue(role.getReviewEnabled(), false));
            ps.setString(13, processInsertValue(role.getNotifyRoles()));
            ps.setString(14, processInsertValue(role.getUserAuthorityFilter()));
            ps.setString(15, processInsertValue(role.getUserAuthorityExpiration()));
            ps.setString(16, processInsertValue(role.getDescription()));
            ps.setInt(17, processInsertValue(role.getGroupExpiryDays()));
            ps.setBoolean(18, processInsertValue(role.getDeleteProtection(), false));
            ps.setTimestamp(19, lastReviewedTime);
            ps.setInt(20, processInsertValue(role.getMaxMembers()));
            ps.setBoolean(21, processInsertValue(role.getSelfRenew(), false));
            ps.setInt(22, processInsertValue(role.getSelfRenewMins()));
            ps.setString(23, processInsertValue(role.getPrincipalDomainFilter()));
            ps.setString(24, processInsertValue(role.getNotifyDetails()));
            ps.setInt(25, roleId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return (affectedRows > 0);
    }

    @Override
    public boolean updateRoleModTimestamp(String domainName, String roleName) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateRoleModTimestamp";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE_MOD_TIMESTAMP)) {
            ps.setInt(1, roleId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateRoleReviewTimestamp(String domainName, String roleName) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateRoleReviewTimestamp";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE_REVIEW_TIMESTAMP)) {
            ps.setInt(1, roleId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateServiceIdentityModTimestamp(String domainName, String serviceName) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateServiceIdentityModTimestamp";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_SERVICE_MOD_TIMESTAMP)) {
            ps.setInt(1, serviceId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteRole(String domainName, String roleName) throws ServerResourceException {

        final String caller = "deleteRole";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ROLE)) {
            ps.setInt(1, domainId);
            ps.setString(2, roleName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public List listRoles(String domainName) throws ServerResourceException {

        final String caller = "listRoles";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        List roles = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    roles.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(roles);
        return roles;
    }

    @Override
    public List listTrustedRolesWithWildcards(String domainName, String roleName, String trustDomainName) throws ServerResourceException {

        final String caller = "listTrustedRolesWithWildcards";

        List roles = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_TRUSTED_ROLES_WITH_WILDCARD)) {
            ps.setString(1, domainName.replace('*', '%'));
            ps.setString(2, roleName.replace('*', '%'));
            ps.setString(3, trustDomainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    roles.add(ResourceUtils.roleResourceName(rs.getString(1), rs.getString(2)));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return roles;
    }

    @Override
    public int countRoles(String domainName) throws ServerResourceException {

        final String caller = "countRoles";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_ROLE)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    public static Comparator RoleMemberComparator = (roleMember1, roleMember2) -> {
        String roleMember1Name = roleMember1.getMemberName().toLowerCase();
        String roleMember2Name = roleMember2.getMemberName().toLowerCase();
        return roleMember1Name.compareTo(roleMember2Name);
    };

    public static Comparator GroupMemberComparator = (groupMember1, groupMember2) -> {
        String groupMember1Name = groupMember1.getMemberName().toLowerCase();
        String groupMember2Name = groupMember2.getMemberName().toLowerCase();
        return groupMember1Name.compareTo(groupMember2Name);
    };

    void getStdRoleMembers(int roleId, List members, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE_MEMBERS)) {
            ps.setInt(1, roleId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    RoleMember roleMember = new RoleMember();
                    roleMember.setMemberName(rs.getString(1));
                    java.sql.Timestamp expiration = rs.getTimestamp(2);
                    if (expiration != null) {
                        roleMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    java.sql.Timestamp reviewReminder = rs.getTimestamp(3);
                    if (reviewReminder != null) {
                        roleMember.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                    }
                    roleMember.setActive(nullIfDefaultValue(rs.getBoolean(4), true));
                    roleMember.setAuditRef(rs.getString(5));
                    roleMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(6), 0));
                    roleMember.setApproved(true);
                    members.add(roleMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    void getPendingRoleMembers(int roleId, List members, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_PENDING_ROLE_MEMBERS)) {
            ps.setInt(1, roleId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    RoleMember roleMember = new RoleMember();
                    roleMember.setMemberName(rs.getString(1));
                    java.sql.Timestamp timestamp = rs.getTimestamp(2);
                    if (timestamp != null) {
                        roleMember.setExpiration(Timestamp.fromMillis(timestamp.getTime()));
                    }
                    timestamp = rs.getTimestamp(3);
                    if (timestamp != null) {
                        roleMember.setReviewReminder(Timestamp.fromMillis(timestamp.getTime()));
                    }
                    timestamp = rs.getTimestamp(4);
                    if (timestamp != null) {
                        roleMember.setRequestTime(Timestamp.fromMillis(timestamp.getTime()));
                    }
                    roleMember.setAuditRef(rs.getString(5));
                    roleMember.setPendingState(rs.getString(6));
                    roleMember.setActive(false);
                    roleMember.setApproved(false);
                    members.add(roleMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    @Override
    public List listRoleMembers(String domainName, String roleName, Boolean pending) throws ServerResourceException {

        final String caller = "listRoleMembers";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }

        // first get our standard role members

        List members = new ArrayList<>();
        getStdRoleMembers(roleId, members, caller);

        // if requested, include pending members as well

        if (pending == Boolean.TRUE) {
            getPendingRoleMembers(roleId, members, caller);
        }

        members.sort(RoleMemberComparator);
        return members;
    }

    @Override
    public int countRoleMembers(String domainName, String roleName) throws ServerResourceException {

        final String caller = "countRoleMembers";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_ROLE_MEMBERS)) {
            ps.setInt(1, roleId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public List listRoleAuditLogs(String domainName, String roleName) throws ServerResourceException {

        final String caller = "listRoleAuditLogs";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        List logs = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE_AUDIT_LOGS)) {
            ps.setInt(1, roleId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    RoleAuditLog log = new RoleAuditLog();
                    log.setAction(rs.getString(JDBCConsts.DB_COLUMN_ACTION));
                    log.setMember(rs.getString(JDBCConsts.DB_COLUMN_MEMBER));
                    log.setAdmin(rs.getString(JDBCConsts.DB_COLUMN_ADMIN));
                    log.setAuditRef(saveValue(rs.getString(JDBCConsts.DB_COLUMN_AUDIT_REF)));
                    log.setCreated(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_CREATED).getTime()));
                    logs.add(log);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return logs;
    }

    boolean parsePrincipal(String principal, StringBuilder domain, StringBuilder name) {
        int idx = principal.lastIndexOf('.');
        if (idx == -1 || idx == 0 || idx == principal.length() - 1) {
            return false;
        }
        domain.append(principal, 0, idx);
        name.append(principal.substring(idx + 1));
        return true;
    }

    boolean getRoleMembership(final String query, int roleId, final String member, long expiration,
            Membership membership, boolean disabledFlagCheck, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setInt(1, roleId);
            ps.setString(2, member);
            if (expiration != 0) {
                ps.setTimestamp(3, new java.sql.Timestamp(expiration));
            }
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    membership.setIsMember(true);
                    java.sql.Timestamp expiry = rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION);
                    if (expiry != null) {
                        membership.setExpiration(Timestamp.fromMillis(expiry.getTime()));
                    }
                    java.sql.Timestamp reviewReminder = rs.getTimestamp(JDBCConsts.DB_COLUMN_REVIEW_REMINDER);
                    if (reviewReminder != null) {
                        membership.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                    }
                    membership.setRequestPrincipal(rs.getString(JDBCConsts.DB_COLUMN_REQ_PRINCIPAL));
                    if (disabledFlagCheck) {
                        membership.setSystemDisabled(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SYSTEM_DISABLED), 0));
                    } else {
                        membership.setPendingState(rs.getString(JDBCConsts.DB_COLUMN_PENDING_STATE));
                    }
                    return true;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return false;
    }

    @Override
    public Membership getRoleMember(String domainName, String roleName, String member,
            long expiration, boolean pending) throws ServerResourceException {

        final String caller = "getRoleMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }

        Membership membership = new Membership()
                .setMemberName(member)
                .setRoleName(ResourceUtils.roleResourceName(domainName, roleName))
                .setIsMember(false);

        // first we're going to check if we have a standard user with the given
        // details before checking for pending unless we're specifically asking
        // for pending member only in which case we'll skip the first check

        if (!pending) {
            String query = expiration == 0 ? SQL_GET_ROLE_MEMBER : SQL_GET_TEMP_ROLE_MEMBER;
            if (getRoleMembership(query, roleId, member, expiration, membership, true, caller)) {
                membership.setApproved(true);
            }
        }

        if (!membership.getIsMember()) {
            String query = expiration == 0 ? SQL_GET_PENDING_ROLE_MEMBER : SQL_GET_TEMP_PENDING_ROLE_MEMBER;
            if (getRoleMembership(query, roleId, member, expiration, membership, false, caller)) {
                membership.setApproved(false);
            }
        }

        return membership;
    }

    int insertPrincipal(String principal) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertPrincipal";

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_PRINCIPAL)) {
            ps.setString(1, principal);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {

            // it's possible that 2 threads try to add the same principal
            // into different roles. so we're going to have a special
            // handling here - if we get back entry already exists exception
            // we're just going to look up the principal id and return
            // that instead of returning an exception. However, if we still
            // get no response for the principal id, then it indicates that
            // the other thread hasn't completed its transaction yet, so
            // we need to return a conflict exception so the server can
            // retry its operation after a short while

            if (ex.getErrorCode() == MYSQL_ER_OPTION_DUPLICATE_ENTRY) {
                int principalId = getPrincipalId(principal);
                if (principalId == 0) {
                    throw sqlError(new SQLException("insert principal lock conflict", MYSQL_EXC_STATE_DEADLOCK), caller);
                }
                return principalId;
            }

            throw sqlError(ex, caller);
        }

        // if we got an expected response of 1 row updated, then we'll
        // pick up the last insert id.

        if (affectedRows == 1) {
            return getLastInsertId();
        }

        // However, if we got back 0 rows updated without the duplicate
        // entry exception, we'll assume that entry exists, and we'll try
        // to fetch it one more time. And if we still get no response,
        // that indicates that the previous transaction hasn't completed
        // yet, so we'll return a conflict exception so the server can
        // retry the operation

        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw sqlError(new SQLException("insert principal lock conflict", MYSQL_EXC_STATE_DEADLOCK), caller);
        }
        return principalId;
    }

    int insertHost(String hostName) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertHost";

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_HOST)) {
            ps.setString(1, hostName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        int hostId = 0;
        if (affectedRows == 1) {
            hostId = getLastInsertId();
        }
        return hostId;
    }

    boolean roleMemberExists(int roleId, int principalId, String principal, String pendingState, final String caller) throws ServerResourceException {
        boolean pending = pendingState != null;
        String statement =  pending ? SQL_PENDING_ROLE_MEMBER_EXISTS : SQL_STD_ROLE_MEMBER_EXISTS;
        try (PreparedStatement ps = con.prepareStatement(statement)) {
            ps.setInt(1, roleId);
            ps.setInt(2, principalId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    if (pending) {
                        String currentState = rs.getString(1);
                        // check current request doesn't contradict the existing one
                        if (currentState != null && !currentState.equals(pendingState)) {
                            throw Utils.requestError("The user " + principal + " already has a pending request in a different state", caller);
                        }
                    }
                    return true;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return false;
    }

    @Override
    public boolean insertRoleMember(String domainName, String roleName, RoleMember roleMember,
            String admin, String auditRef) throws ServerResourceException {

        final String caller = "insertRoleMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        String principal = roleMember.getMemberName();
        if (!validatePrincipalDomain(principal)) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, principal);
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            principalId = insertPrincipal(principal);
            if (principalId == 0) {
                throw internalServerError(caller, "Unable to insert principal: " + principal);
            }
        }

        // need to check if entry already exists

        boolean pendingRequest = (roleMember.getApproved() == Boolean.FALSE);
        boolean roleMemberExists = roleMemberExists(roleId, principalId, principal, roleMember.getPendingState(), caller);

        // process the request based on the type of the request
        // either pending request or standard insert

        boolean result;
        if (pendingRequest) {
            result = insertPendingRoleMember(roleId, principalId, roleMember, admin,
                    principal, auditRef, roleMemberExists, caller);
        } else {
            result = insertStandardRoleMember(roleId, principalId, roleMember, admin,
                    principal, auditRef, roleMemberExists, false, caller);
        }
        return result;
    }

    boolean insertPendingRoleMember(int roleId, int principalId, RoleMember roleMember,
            final String admin, final String principal, final String auditRef, boolean roleMemberExists,
            final String caller) throws ServerResourceException {

        java.sql.Timestamp expiration = roleMember.getExpiration() == null ? null :
                new java.sql.Timestamp(roleMember.getExpiration().millis());

        java.sql.Timestamp reviewReminder = roleMember.getReviewReminder() == null ? null :
                new java.sql.Timestamp(roleMember.getReviewReminder().millis());

        int affectedRows;
        if (roleMemberExists) {
            try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PENDING_ROLE_MEMBER)) {
                ps.setTimestamp(1, expiration);
                ps.setTimestamp(2, reviewReminder);
                ps.setString(3, processInsertValue(auditRef));
                ps.setString(4, processInsertValue(admin));
                ps.setInt(5, roleId);
                ps.setInt(6, principalId);
                affectedRows = executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }

        } else {

            try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_PENDING_ROLE_MEMBER)) {
                ps.setInt(1, roleId);
                ps.setInt(2, principalId);
                ps.setTimestamp(3, expiration);
                ps.setTimestamp(4, reviewReminder);
                ps.setString(5, processInsertValue(auditRef));
                ps.setString(6, processInsertValue(admin));
                ps.setString(7, roleMember.getPendingState());
                affectedRows = executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }

        // add audit log entry for this change if the operation was successful
        // add return the result of the audit log insert operation

        boolean result = affectedRows > 0;
        if (result) {
            result = insertRoleAuditLog(roleId, admin, principal, AUDIT_OPERATION_REQUEST, auditRef);
        }

        return result;
    }

    boolean insertStandardRoleMember(int roleId, int principalId, RoleMember roleMember,
            final String admin, final String principal, final String auditRef,
            boolean roleMemberExists, boolean approveRequest, final String caller) throws ServerResourceException {

        java.sql.Timestamp expiration = roleMember.getExpiration() == null ? null :
                new java.sql.Timestamp(roleMember.getExpiration().millis());

        java.sql.Timestamp reviewReminder = roleMember.getReviewReminder() == null ? null :
                new java.sql.Timestamp(roleMember.getReviewReminder().millis());

        boolean result;
        String auditOperation;

        if (roleMemberExists) {

            try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE_MEMBER)) {
                ps.setTimestamp(1, expiration);
                ps.setTimestamp(2, reviewReminder);
                ps.setBoolean(3, processInsertValue(roleMember.getActive(), true));
                ps.setString(4, processInsertValue(auditRef));
                ps.setString(5, processInsertValue(admin));
                ps.setInt(6, roleId);
                ps.setInt(7, principalId);
                executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
            auditOperation = approveRequest ? AUDIT_OPERATION_APPROVE : AUDIT_OPERATION_UPDATE;
            result = true;

        } else {

            int affectedRows;
            try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE_MEMBER)) {
                ps.setInt(1, roleId);
                ps.setInt(2, principalId);
                ps.setTimestamp(3, expiration);
                ps.setTimestamp(4, reviewReminder);
                ps.setBoolean(5, processInsertValue(roleMember.getActive(), true));
                ps.setString(6, processInsertValue(auditRef));
                ps.setString(7, processInsertValue(admin));
                affectedRows = executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }

            auditOperation = approveRequest ? AUDIT_OPERATION_APPROVE : AUDIT_OPERATION_ADD;
            result = (affectedRows > 0);
        }

        // add audit log entry for this change if the operation was successful
        // add return the result of the audit log insert operation

        if (result) {
            result = insertRoleAuditLog(roleId, admin, principal, auditOperation, auditRef);
        }
        return result;
    }

    @Override
    public boolean updateRoleMemberDisabledState(String domainName, String roleName, String principal,
            String admin, int disabledState, String auditRef) throws ServerResourceException {

        final String caller = "updateRoleMemberDisabledState";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ROLE_MEMBER_DISABLED_STATE)) {
            ps.setInt(1, disabledState);
            ps.setString(2, processInsertValue(auditRef));
            ps.setString(3, processInsertValue(admin));
            ps.setInt(4, roleId);
            ps.setInt(5, principalId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        // add audit log entry for this change if the disable was successful
        // add return the result of the audit log insert operation

        if (result) {
            final String operation = disabledState == 0 ? "ENABLE" : "DISABLE";
            result = insertRoleAuditLog(roleId, admin, principal, operation, auditRef);
        }

        return result;
    }

    @Override
    public boolean deleteRoleMember(String domainName, String roleName, String principal,
            String admin, String auditRef) throws ServerResourceException {

        final String caller = "deleteRoleMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ROLE_MEMBER)) {
            ps.setInt(1, roleId);
            ps.setInt(2, principalId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        // add audit log entry for this change if the delete was successful
        // add return the result of the audit log insert operation

        if (result) {
            result = insertRoleAuditLog(roleId, admin, principal, "DELETE", auditRef);
        }

        return result;
    }

    @Override
    public boolean deleteExpiredRoleMember(String domainName, String roleName, String principal,
                                           String admin, Timestamp expiration, String auditRef) throws ServerResourceException {
        final String caller = "deleteRoleMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        java.sql.Timestamp ts = new java.sql.Timestamp(expiration.millis());

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_EXPIRED_ROLE_MEMBER)) {
            ps.setInt(1, roleId);
            ps.setInt(2, principalId);
            ps.setTimestamp(3, ts);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        // add audit log entry for this change if the delete was successful
        // add return the result of the audit log insert operation

        if (result) {
            result = insertRoleAuditLog(roleId, admin, principal, "DELETE", auditRef);
        }

        return result;
    }

    boolean insertRoleAuditLog(int roleId, String admin, String member,
            String action, String auditRef) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertRoleAuditEntry";

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE_AUDIT_LOG)) {
            ps.setInt(1, roleId);
            ps.setString(2, processInsertValue(admin));
            ps.setString(3, member);
            ps.setString(4, action);
            ps.setString(5, processInsertValue(auditRef));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public Assertion getAssertion(String domainName, String policyName, Long assertionId) throws ServerResourceException {

        final String caller = "getAssertion";

        Assertion assertion = null;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ASSERTION)) {
            ps.setInt(1, assertionId.intValue());
            ps.setString(2, domainName);
            ps.setString(3, policyName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    assertion = new Assertion();
                    assertion.setRole(ResourceUtils.roleResourceName(domainName, rs.getString(JDBCConsts.DB_COLUMN_ROLE)));
                    assertion.setResource(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE));
                    assertion.setAction(rs.getString(JDBCConsts.DB_COLUMN_ACTION));
                    assertion.setEffect(AssertionEffect.valueOf(rs.getString(JDBCConsts.DB_COLUMN_EFFECT)));
                    assertion.setId((long) rs.getInt(JDBCConsts.DB_COLUMN_ASSERT_ID));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return assertion;
    }

    @Override
    public Policy getPolicy(String domainName, String policyName, String version) throws ServerResourceException {

        final String caller = "getPolicy";

        try (PreparedStatement ps = con.prepareStatement((StringUtil.isEmpty(version)) ? SQL_GET_POLICY : SQL_GET_POLICY_VERSION)) {
            ps.setString(1, domainName);
            ps.setString(2, policyName);
            if (!StringUtil.isEmpty(version)) {
                ps.setString(3, version);
            }
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return savePolicySettings(domainName, policyName, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    @Override
    public boolean insertPolicy(String domainName, Policy policy) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertPolicy";

        String policyName = Utils.extractPolicyName(domainName, policy.getName());
        if (policyName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " insert policy name: " + policy.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        try (PreparedStatement ps = con.prepareStatement(!StringUtil.isEmpty(policy.getVersion()) ? SQL_INSERT_POLICY_VERSION : SQL_INSERT_POLICY)) {
            ps.setString(1, policyName);
            ps.setInt(2, domainId);
            if (!StringUtil.isEmpty(policy.getVersion())) {
                ps.setString(3, policy.getVersion());
                ps.setBoolean(4, policy.getActive());
            }
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updatePolicy(String domainName, Policy policy) throws ServerResourceException {

        int affectedRows;
        final String caller = "updatePolicy";

        String policyName = Utils.extractPolicyName(domainName, policy.getName());
        if (policyName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " update policy name: " + policy.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, policy.getVersion());
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_POLICY)) {
            ps.setString(1, policyName);
            ps.setInt(2, policyId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updatePolicyModTimestamp(String domainName, String policyName, String version) throws ServerResourceException {

        int affectedRows;
        final String caller = "updatePolicyModTimestamp";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_POLICY_MOD_TIMESTAMP)) {
            ps.setInt(1, policyId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean setActivePolicyVersion(String domainName, String policyName, String version) throws ServerResourceException {

        int affectedRows;
        final String caller = "setActivePolicyVersion";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_SET_ACTIVE_POLICY_VERSION)) {
            ps.setString(1, version);
            ps.setInt(2, domainId);
            ps.setString(3, policyName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }


    @Override
    public boolean deletePolicy(String domainName, String policyName) throws ServerResourceException {

        final String caller = "deletePolicy";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_POLICY)) {
            ps.setInt(1, domainId);
            ps.setString(2, policyName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deletePolicyVersion(String domainName, String policyName, String version) throws ServerResourceException {

        final String caller = "deletePolicyVersion";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_POLICY_VERSION)) {
            ps.setInt(1, domainId);
            ps.setString(2, policyName);
            ps.setString(3, version);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public List listPolicies(String domainName, String assertionRoleName) throws ServerResourceException {

        final String caller = "listPolicies";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        List policies = new ArrayList<>();
        final String sqlStatement = (assertionRoleName == null) ? SQL_LIST_POLICY : SQL_LIST_POLICY_REFERENCING_ROLE;
        try (PreparedStatement ps = con.prepareStatement(sqlStatement)) {
            ps.setInt(1, domainId);
            if (assertionRoleName != null) {
                ps.setString(2, assertionRoleName);
            }
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    policies.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(policies);
        return policies;
    }

    @Override
    public List listPolicyVersions(String domainName, String policyName) throws ServerResourceException {
        final String caller = "listPolicyVersions";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, null);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }
        List policies = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_POLICY_VERSION)) {
            ps.setInt(1, domainId);
            ps.setString(2, policyName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    policies.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(policies);
        return policies;
    }

    @Override
    public int countPolicies(String domainName) throws ServerResourceException {

        final String caller = "countPolicies";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_POLICY)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public boolean insertAssertion(String domainName, String policyName, String version, Assertion assertion) throws ServerResourceException {

        final String caller = "insertAssertion";

        String roleName = Utils.extractRoleName(domainName, assertion.getRole());
        if (roleName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " assertion role name: " + assertion.getRole());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }

        // special handling for assertions since we don't want to have duplicates
        // and we don't want to setup a unique key across all values in the row

        try (PreparedStatement ps = con.prepareStatement(SQL_CHECK_ASSERTION)) {
            ps.setInt(1, policyId);
            ps.setString(2, roleName);
            ps.setString(3, assertion.getResource());
            ps.setString(4, assertion.getAction());
            ps.setString(5, processInsertValue(assertion.getEffect()));
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return true;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // at this point we know we don't have another assertion with the same
        // values so we'll go ahead and add one

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ASSERTION)) {
            ps.setInt(1, policyId);
            ps.setString(2, roleName);
            ps.setString(3, assertion.getResource());
            ps.setString(4, assertion.getAction());
            ps.setString(5, processInsertValue(assertion.getEffect()));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        if (result) {
            assertion.setId((long) getLastInsertId());
        }
        return result;
    }

    @Override
    public boolean deleteAssertion(String domainName, String policyName, String version, Long assertionId) throws ServerResourceException {

        final String caller = "deleteAssertion";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ASSERTION)) {
            ps.setInt(1, policyId);
            ps.setInt(2, assertionId.intValue());
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public List listAssertions(String domainName, String policyName, String version) throws ServerResourceException {

        final String caller = "listAssertions";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }

        // assertion fetch
        List assertions = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ASSERTION)) {
            ps.setInt(1, policyId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    Assertion assertion = new Assertion();
                    assertion.setRole(ResourceUtils.roleResourceName(domainName, rs.getString(JDBCConsts.DB_COLUMN_ROLE)));
                    assertion.setResource(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE));
                    assertion.setAction(rs.getString(JDBCConsts.DB_COLUMN_ACTION));
                    assertion.setEffect(AssertionEffect.valueOf(rs.getString(JDBCConsts.DB_COLUMN_EFFECT)));
                    assertion.setId((long) rs.getInt(JDBCConsts.DB_COLUMN_ASSERT_ID));
                    assertions.add(assertion);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // assertion conditions fetch
        Map assertionsMap = assertions.stream().collect(Collectors.toMap(Assertion::getId, assertion -> assertion));
        Map assertionConditionMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_POLICY_ASSERTIONS_CONDITIONS)) {
            ps.setInt(1, policyId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    long assertionId = rs.getLong(JDBCConsts.DB_COLUMN_ASSERT_ID);
                    Assertion assertion = assertionsMap.get(assertionId);
                    if (assertion == null) {
                        continue;
                    }
                    AssertionConditions assertionConditions = assertion.getConditions();
                    if (assertionConditions == null) {
                        assertionConditions = new AssertionConditions();
                        List assertionConditionList = new ArrayList<>();
                        assertionConditions.setConditionsList(assertionConditionList);
                        assertion.setConditions(assertionConditions);
                    }
                    int conditionId = rs.getInt(JDBCConsts.DB_COLUMN_CONDITION_ID);
                    AssertionCondition assertionCondition = assertionConditionMap.get(assertionId + ":" + conditionId);
                    if (assertionCondition == null) {
                        assertionCondition = new AssertionCondition();
                        Map assertionConditionDataMap = new HashMap<>();
                        assertionCondition.setConditionsMap(assertionConditionDataMap);
                        assertionCondition.setId(conditionId);
                        assertionConditionMap.put(assertionId + ":" + conditionId, assertionCondition);
                        assertionConditions.getConditionsList().add(assertionCondition);
                    }
                    AssertionConditionData assertionConditionData = new AssertionConditionData();
                    if (rs.getString(JDBCConsts.DB_COLUMN_OPERATOR) != null) {
                        assertionConditionData.setOperator(AssertionConditionOperator.fromString(rs.getString(JDBCConsts.DB_COLUMN_OPERATOR)));
                    }
                    assertionConditionData.setValue(rs.getString(JDBCConsts.DB_COLUMN_VALUE));
                    assertionCondition.getConditionsMap().put(rs.getString(JDBCConsts.DB_COLUMN_KEY), assertionConditionData);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return assertions;
    }

    @Override
    public int countAssertions(String domainName, String policyName, String version) throws ServerResourceException {

        final String caller = "countAssertions";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_ASSERTION)) {
            ps.setInt(1, policyId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    String saveValue(String value) {
        return (value.isEmpty()) ? null : value;
    }

    UUID saveUuidValue(String value) {
        return (value.isEmpty()) ? null : UUID.fromString(value);
    }

    @Override
    public ServiceIdentity getServiceIdentity(String domainName, String serviceName) throws ServerResourceException {

        final String caller = "getServiceIdentity";

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_SERVICE)) {
            ps.setString(1, domainName);
            ps.setString(2, serviceName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return saveServiceIdentitySettings(domainName, serviceName, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    ServiceIdentity saveServiceIdentitySettings(final String domainName, final String serviceName,
            ResultSet rs) throws SQLException {

        return new ServiceIdentity()
                .setName(ResourceUtils.serviceResourceName(domainName, serviceName))
                .setDescription(saveValue(rs.getString(JDBCConsts.DB_COLUMN_DESCRIPTION)))
                .setModified(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime()))
                .setProviderEndpoint(saveValue(rs.getString(JDBCConsts.DB_COLUMN_PROVIDER_ENDPOINT)))
                .setExecutable(saveValue(rs.getString(JDBCConsts.DB_COLUMN_EXECUTABLE)))
                .setUser(saveValue(rs.getString(JDBCConsts.DB_COLUMN_SVC_USER)))
                .setGroup(saveValue(rs.getString(JDBCConsts.DB_COLUMN_SVC_GROUP)))
                .setResourceOwnership(ResourceOwnership.getResourceServiceOwnership(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE_OWNER)));
    }

    int processInsertValue(Integer value) {
        return (value == null) ? 0 : value;
    }

    String processInsertValue(String value) {
        return (value == null) ? "" : value.trim();
    }

    boolean processInsertValue(Boolean value, boolean defaultValue) {
        return (value == null) ? defaultValue : value;
    }

    String processInsertValue(AssertionEffect value) {
        return (value == null) ? JDBCConsts.ASSERTION_EFFECT_ALLOW : value.toString();
    }

    String processInsertUuidValue(UUID value) {
        return (value == null) ? "" : value.toString();
    }

    @Override
    public boolean insertServiceIdentity(String domainName, ServiceIdentity service) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertServiceIdentity";

        String serviceName = Utils.extractServiceName(domainName, service.getName());
        if (serviceName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " insert service name: " + service.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_SERVICE)) {
            ps.setString(1, serviceName);
            ps.setString(2, processInsertValue(service.getDescription()));
            ps.setString(3, processInsertValue(service.getProviderEndpoint()));
            ps.setString(4, processInsertValue(service.getExecutable()));
            ps.setString(5, processInsertValue(service.getUser()));
            ps.setString(6, processInsertValue(service.getGroup()));
            ps.setInt(7, domainId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateServiceIdentity(String domainName, ServiceIdentity service) throws ServerResourceException {

        int affectedRows;
        final String caller = "updateServiceIdentity";

        String serviceName = Utils.extractServiceName(domainName, service.getName());
        if (serviceName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " update service name: " + service.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_SERVICE)) {
            ps.setString(1, processInsertValue(service.getDescription()));
            ps.setString(2, processInsertValue(service.getProviderEndpoint()));
            ps.setString(3, processInsertValue(service.getExecutable()));
            ps.setString(4, processInsertValue(service.getUser()));
            ps.setString(5, processInsertValue(service.getGroup()));
            ps.setInt(6, serviceId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteServiceIdentity(String domainName, String serviceName) throws ServerResourceException {

        final String caller = "deleteServiceIdentity";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_SERVICE)) {
            ps.setInt(1, domainId);
            ps.setString(2, serviceName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public List listServiceIdentities(String domainName) throws ServerResourceException {

        final String caller = "listServiceIdentities";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        List services = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_SERVICE)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    services.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(services);
        return services;
    }

    @Override
    public int countServiceIdentities(String domainName) throws ServerResourceException {

        final String caller = "countServiceIdentities";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_SERVICE)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public List listPublicKeys(String domainName, String serviceName) throws ServerResourceException {

        final String caller = "listPublicKeys";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        List publicKeys = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_PUBLIC_KEY)) {
            ps.setInt(1, serviceId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    PublicKeyEntry publicKey = new PublicKeyEntry()
                            .setId(rs.getString(JDBCConsts.DB_COLUMN_KEY_ID))
                            .setKey(rs.getString(JDBCConsts.DB_COLUMN_KEY_VALUE));
                    publicKeys.add(publicKey);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return publicKeys;
    }

    @Override
    public int countPublicKeys(String domainName, String serviceName) throws ServerResourceException {

        final String caller = "countPublicKeys";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_PUBLIC_KEY)) {
            ps.setInt(1, serviceId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public PublicKeyEntry getPublicKeyEntry(String domainName, String serviceName,
            String keyId, boolean domainStateCheck) throws ServerResourceException {

        final String caller = "getPublicKeyEntry";

        int domainId = getDomainId(domainName, domainStateCheck);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PUBLIC_KEY)) {
            ps.setInt(1, serviceId);
            ps.setString(2, keyId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return new PublicKeyEntry().setId(keyId)
                            .setKey(rs.getString(JDBCConsts.DB_COLUMN_KEY_VALUE));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    @Override
    public boolean insertPublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey) throws ServerResourceException {

        final String caller = "insertPublicKeyEntry";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_PUBLIC_KEY)) {
            ps.setInt(1, serviceId);
            ps.setString(2, publicKey.getId());
            ps.setString(3, publicKey.getKey());
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updatePublicKeyEntry(String domainName, String serviceName, PublicKeyEntry publicKey) throws ServerResourceException {

        final String caller = "updatePublicKeyEntry";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PUBLIC_KEY)) {
            ps.setString(1, publicKey.getKey());
            ps.setInt(2, serviceId);
            ps.setString(3, publicKey.getId());
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deletePublicKeyEntry(String domainName, String serviceName, String keyId) throws ServerResourceException {

        final String caller = "deletePublicKeyEntry";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_PUBLIC_KEY)) {
            ps.setInt(1, serviceId);
            ps.setString(2, keyId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public List listServiceHosts(String domainName, String serviceName) throws ServerResourceException {

        final String caller = "listServiceHosts";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        List hosts = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_SERVICE_HOST)) {
            ps.setInt(1, serviceId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    hosts.add(rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return hosts;
    }

    @Override
    public boolean insertServiceHost(String domainName, String serviceName, String hostName) throws ServerResourceException {

        final String caller = "insertServiceHost";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int hostId = getHostId(hostName);
        if (hostId == 0) {
            hostId = insertHost(hostName);
            if (hostId == 0) {
                throw internalServerError(caller, "Unable to insert host: " + hostName);
            }
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_SERVICE_HOST)) {
            ps.setInt(1, serviceId);
            ps.setInt(2, hostId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteServiceHost(String domainName, String serviceName, String hostName) throws ServerResourceException {

        final String caller = "deleteServiceHost";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int hostId = getHostId(hostName);
        if (hostId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_HOST, hostName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_SERVICE_HOST)) {
            ps.setInt(1, serviceId);
            ps.setInt(2, hostId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean insertEntity(String domainName, Entity entity) throws ServerResourceException {

        final String caller = "insertEntity";

        String entityName = Utils.extractEntityName(domainName, entity.getName());
        if (entityName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " insert entity name: " + entity.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ENTITY)) {
            ps.setInt(1, domainId);
            ps.setString(2, entityName);
            ps.setString(3, JSON.string(entity.getValue()));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateEntity(String domainName, Entity entity) throws ServerResourceException {

        final String caller = "updateEntity";

        String entityName = Utils.extractEntityName(domainName, entity.getName());
        if (entityName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " insert entity name: " + entity.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_ENTITY)) {
            ps.setString(1, JSON.string(entity.getValue()));
            ps.setInt(2, domainId);
            ps.setString(3, entityName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteEntity(String domainName, String entityName) throws ServerResourceException {

        final String caller = "deleteEntity";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ENTITY)) {
            ps.setInt(1, domainId);
            ps.setString(2, entityName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public Entity getEntity(String domainName, String entityName) throws ServerResourceException {

        final String caller = "getEntity";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ENTITY)) {
            ps.setInt(1, domainId);
            ps.setString(2, entityName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return new Entity().setName(ResourceUtils.entityResourceName(domainName, entityName))
                            .setValue(JSON.fromString(rs.getString(JDBCConsts.DB_COLUMN_VALUE), Struct.class));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    @Override
    public List listEntities(String domainName) throws ServerResourceException {

        final String caller = "listEntities";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        List entities = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ENTITY)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    entities.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(entities);
        return entities;
    }

    @Override
    public int countEntities(String domainName) throws ServerResourceException {

        final String caller = "countEntities";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_ENTITY)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    Role retrieveRole(ResultSet rs, final String domainName, final String roleName) throws SQLException {
        Role role = new Role().setName(ResourceUtils.roleResourceName(domainName, roleName))
                .setModified(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime()))
                .setTrust(saveValue(rs.getString(JDBCConsts.DB_COLUMN_TRUST)))
                .setAuditEnabled(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_AUDIT_ENABLED), false))
                .setSelfServe(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_SELF_SERVE), false))
                .setMemberExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_EXPIRY_DAYS), 0))
                .setTokenExpiryMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_TOKEN_EXPIRY_MINS), 0))
                .setCertExpiryMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_CERT_EXPIRY_MINS), 0))
                .setSignAlgorithm(saveValue(rs.getString(JDBCConsts.DB_COLUMN_SIGN_ALGORITHM)))
                .setServiceExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_EXPIRY_DAYS), 0))
                .setGroupExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_GROUP_EXPIRY_DAYS), 0))
                .setReviewEnabled(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_REVIEW_ENABLED), false))
                .setDeleteProtection(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_DELETE_PROTECTION), false))
                .setMemberReviewDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_REVIEW_DAYS), 0))
                .setServiceReviewDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_REVIEW_DAYS), 0))
                .setGroupReviewDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_GROUP_REVIEW_DAYS), 0))
                .setNotifyRoles(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_ROLES)))
                .setNotifyDetails(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_DETAILS)))
                .setUserAuthorityFilter(saveValue(rs.getString(JDBCConsts.DB_COLUMN_USER_AUTHORITY_FILTER)))
                .setUserAuthorityExpiration(saveValue(rs.getString(JDBCConsts.DB_COLUMN_USER_AUTHORITY_EXPIRATION)))
                .setDescription(saveValue(rs.getString(JDBCConsts.DB_COLUMN_DESCRIPTION)))
                .setMaxMembers(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MAX_MEMBERS), 0))
                .setSelfRenew(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_SELF_RENEW), false))
                .setSelfRenewMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SELF_RENEW_MINS), 0))
                .setResourceOwnership(ResourceOwnership.getResourceRoleOwnership(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE_OWNER)))
                .setPrincipalDomainFilter(saveValue(rs.getString(JDBCConsts.DB_COLUMN_PRINCIPAL_DOMAIN_FILTER)));
        java.sql.Timestamp lastReviewedTime = rs.getTimestamp(JDBCConsts.DB_COLUMN_LAST_REVIEWED_TIME);
        if (lastReviewedTime != null) {
            role.setLastReviewedDate(Timestamp.fromMillis(lastReviewedTime.getTime()));
        }
        return role;
    }

    void getAthenzDomainRoles(String domainName, int domainId, AthenzDomain athenzDomain) throws ServerResourceException {

        final String caller = "getAthenzDomain";
        Map roleMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ROLES)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String roleName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    Role role = retrieveRole(rs, domainName, roleName);
                    roleMap.put(roleName, role);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ROLE_MEMBERS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String roleName = rs.getString(1);
                    Role role = roleMap.get(roleName);
                    if (role == null) {
                        continue;
                    }
                    List members = role.getRoleMembers();
                    if (members == null) {
                        members = new ArrayList<>();
                        role.setRoleMembers(members);
                    }
                    RoleMember roleMember = new RoleMember();
                    roleMember.setMemberName(rs.getString(2));
                    java.sql.Timestamp expiration = rs.getTimestamp(3);
                    if (expiration != null) {
                        roleMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    java.sql.Timestamp reviewReminder = rs.getTimestamp(4);
                    if (reviewReminder != null) {
                        roleMember.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                    }
                    roleMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(5), 0));
                    members.add(roleMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // add role tags
        addTagsToRoles(roleMap, athenzDomain.getName());

        athenzDomain.getRoles().addAll(roleMap.values());
    }

    void getAthenzDomainGroups(String domainName, int domainId, AthenzDomain athenzDomain) throws ServerResourceException {

        final String caller = "getAthenzDomain";
        Map groupMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_GROUPS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String groupName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    Group group = retrieveGroup(rs, domainName, groupName);
                    groupMap.put(groupName, group);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_GROUP_MEMBERS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String groupName = rs.getString(1);
                    Group group = groupMap.get(groupName);
                    if (group == null) {
                        continue;
                    }
                    List members = group.getGroupMembers();
                    if (members == null) {
                        members = new ArrayList<>();
                        group.setGroupMembers(members);
                    }
                    GroupMember groupMember = new GroupMember();
                    groupMember.setMemberName(rs.getString(2));
                    groupMember.setGroupName(group.getName());
                    java.sql.Timestamp expiration = rs.getTimestamp(3);
                    if (expiration != null) {
                        groupMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    groupMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(4), 0));
                    members.add(groupMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // add group tags
        addTagsToGroups(groupMap, athenzDomain.getName());

        athenzDomain.getGroups().addAll(groupMap.values());
    }

    void getAthenzDomainPolicies(String domainName, int domainId, AthenzDomain athenzDomain) throws ServerResourceException {

        final String caller = "getAthenzDomain";
        Map policyMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_POLICIES)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    int policyId = rs.getInt(JDBCConsts.DB_COLUMN_POLICY_ID);
                    final String policyName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    policyMap.put(policyId, savePolicySettings(domainName, policyName, rs));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        Map assertionsMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_POLICY_ASSERTIONS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    int policyId = rs.getInt(JDBCConsts.DB_COLUMN_POLICY_ID);
                    Policy policy = policyMap.get(policyId);
                    if (policy == null) {
                        continue;
                    }
                    List assertions = policy.getAssertions();
                    if (assertions == null) {
                        assertions = new ArrayList<>();
                        policy.setAssertions(assertions);
                    }
                    Assertion assertion = new Assertion();
                    assertion.setRole(ResourceUtils.roleResourceName(domainName, rs.getString(JDBCConsts.DB_COLUMN_ROLE)));
                    assertion.setResource(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE));
                    assertion.setAction(rs.getString(JDBCConsts.DB_COLUMN_ACTION));
                    assertion.setEffect(AssertionEffect.valueOf(rs.getString(JDBCConsts.DB_COLUMN_EFFECT)));
                    assertion.setId(rs.getLong(JDBCConsts.DB_COLUMN_ASSERT_ID));

                    assertions.add(assertion);
                    assertionsMap.put(assertion.getId(), assertion);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // assertion conditions fetch

        Map assertionConditionMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_POLICY_ASSERTIONS_CONDITIONS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    long assertionId = rs.getLong(JDBCConsts.DB_COLUMN_ASSERT_ID);
                    Assertion assertion = assertionsMap.get(assertionId);
                    if (assertion == null) {
                        continue;
                    }
                    AssertionConditions assertionConditions = assertion.getConditions();
                    if (assertionConditions == null) {
                        assertionConditions = new AssertionConditions();
                        List assertionConditionList = new ArrayList<>();
                        assertionConditions.setConditionsList(assertionConditionList);
                        assertion.setConditions(assertionConditions);
                    }
                    int conditionId = rs.getInt(JDBCConsts.DB_COLUMN_CONDITION_ID);
                    AssertionCondition assertionCondition = assertionConditionMap.get(assertionId + ":" + conditionId);
                    if (assertionCondition == null) {
                        assertionCondition = new AssertionCondition();
                        Map assertionConditionDataMap = new HashMap<>();
                        assertionCondition.setConditionsMap(assertionConditionDataMap);
                        assertionCondition.setId(conditionId);
                        assertionConditionMap.put(assertionId + ":" + conditionId, assertionCondition);
                        assertionConditions.getConditionsList().add(assertionCondition);
                    }
                    AssertionConditionData assertionConditionData = new AssertionConditionData();
                    if (rs.getString(JDBCConsts.DB_COLUMN_OPERATOR) != null) {
                        assertionConditionData.setOperator(AssertionConditionOperator.fromString(rs.getString(JDBCConsts.DB_COLUMN_OPERATOR)));
                    }
                    assertionConditionData.setValue(rs.getString(JDBCConsts.DB_COLUMN_VALUE));
                    assertionCondition.getConditionsMap().put(rs.getString(JDBCConsts.DB_COLUMN_KEY), assertionConditionData);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // add policies tags
        addTagsToPolicies(policyMap, athenzDomain.getName());

        athenzDomain.getPolicies().addAll(policyMap.values());
    }

    void addTagsToPolicies(Map policyMap, String domainName) throws ServerResourceException {

        Map> domainPolicyTags = getDomainPolicyTags(domainName);
        if (domainPolicyTags != null) {
            for (Map.Entry policyEntry : policyMap.entrySet()) {
                Map policyTag = domainPolicyTags.get(Utils.extractPolicyName(domainName, policyEntry.getValue().name) + ":" + policyEntry.getValue().getVersion());
                if (policyTag != null) {
                    policyEntry.getValue().setTags(policyTag);
                }
            }
        }
    }

    Map> getDomainPolicyTags(String domainName) throws ServerResourceException {
        final String funcCaller = "getDomainPolicyTags";
        Map> domainResourceTags = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_POLICY_TAGS)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, funcCaller)) {
                while (rs.next()) {
                    String resourceName = rs.getString(1);
                    String tagKey = rs.getString(2);
                    String tagValue = rs.getString(3);
                    String version = rs.getString(4);
                    if (domainResourceTags == null) {
                        domainResourceTags = new HashMap<>();
                    }
                    Map resourceTag = domainResourceTags.computeIfAbsent(resourceName + ":" + version, tags -> new HashMap<>());
                    TagValueList tagValues = resourceTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, funcCaller);
        }
        return domainResourceTags;
    }

    @Override
    public boolean insertPolicyTags(String policyName, String domainName, Map policyTags, String version) throws ServerResourceException {
        final String caller = "insertPolicyTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }
        int curTagCount = getPolicyTagsCount(policyId);
        int newTagCount = calculateTagCount(policyTags);
        if (curTagCount + newTagCount > policyTagsLimit) {
            throw requestError(caller, "policy tag quota exceeded - limit: "
                    + policyTagsLimit + ", current tags count: " + curTagCount + ", new tags count: " + newTagCount);
        }

        boolean res = true;
        for (Map.Entry e : policyTags.entrySet()) {
            for (String tagValue : e.getValue().getList()) {
                try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_POLICY_TAG)) {
                    ps.setInt(1, policyId);
                    ps.setString(2, processInsertValue(e.getKey()));
                    ps.setString(3, processInsertValue(tagValue));
                    res &= (executeUpdate(ps, caller) > 0);
                } catch (SQLException ex) {
                    throw sqlError(ex, caller);
                }
            }
        }
        return res;
    }

    int getPolicyTagsCount(int policyId) throws ServerResourceException {
        final String caller = "getPolicyTagsCount";
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_POLICY_TAG_COUNT)) {
            ps.setInt(1, policyId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public boolean deletePolicyTags(String policyName, String domainName, Set tagKeys, String version) throws ServerResourceException {
        final String caller = "deletePolicyTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }
        boolean res = true;
        for (String tagKey : tagKeys) {
            try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_POLICY_TAG)) {
                ps.setInt(1, policyId);
                ps.setString(2, processInsertValue(tagKey));
                res &= (executeUpdate(ps, caller) > 0);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
        return res;
    }

    @Override
    public Map getPolicyTags(String domainName, String policyName, String version) throws ServerResourceException {

        final String caller = "getPolicyTags";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int policyId = getPolicyId(domainId, policyName, version);
        if (policyId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_POLICY, ResourceUtils.policyResourceName(domainName, policyName));
        }
        Map policyTag = null;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_POLICY_TAGS)) {
            ps.setInt(1, policyId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String tagKey = rs.getString(1);
                    String tagValue = rs.getString(2);
                    if (policyTag == null) {
                        policyTag = new HashMap<>();
                    }
                    TagValueList tagValues = policyTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return policyTag;
    }

    void getAthenzDomainServices(String domainName, int domainId, AthenzDomain athenzDomain) throws ServerResourceException {

        final String caller = "getAthenzDomain";
        Map serviceMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICES)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String serviceName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    ServiceIdentity service = saveServiceIdentitySettings(domainName, serviceName, rs);
                    List publicKeys = new ArrayList<>();
                    service.setPublicKeys(publicKeys);
                    serviceMap.put(serviceName, service);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICES_HOSTS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String serviceName = rs.getString(1);
                    ServiceIdentity service = serviceMap.get(serviceName);
                    if (service == null) {
                        continue;
                    }
                    List hosts = service.getHosts();
                    if (hosts == null) {
                        hosts = new ArrayList<>();
                        service.setHosts(hosts);
                    }
                    hosts.add(rs.getString(2));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICES_PUBLIC_KEYS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String serviceName = rs.getString(1);
                    ServiceIdentity service = serviceMap.get(serviceName);
                    if (service == null) {
                        continue;
                    }
                    PublicKeyEntry publicKey = new PublicKeyEntry()
                            .setId(rs.getString(JDBCConsts.DB_COLUMN_KEY_ID))
                            .setKey(rs.getString(JDBCConsts.DB_COLUMN_KEY_VALUE));
                    service.getPublicKeys().add(publicKey);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // add services tags
        addTagsToServices(serviceMap, athenzDomain.getName());

        athenzDomain.getServices().addAll(serviceMap.values());
    }

    void getAthenzDomainEntities(String domainName, int domainId, AthenzDomain athenzDomain) throws ServerResourceException {

        final String caller = "getAthenzDomain";
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ENTITIES)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    athenzDomain.getEntities().add(new Entity()
                            .setName(ResourceUtils.entityResourceName(domainName, rs.getString(JDBCConsts.DB_COLUMN_NAME)))
                            .setValue(JSON.fromString(rs.getString(JDBCConsts.DB_COLUMN_VALUE), Struct.class)));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    @Override
    public AthenzDomain getAthenzDomain(String domainName) throws ServerResourceException {

        final String caller = "getAthenzDomain";

        int domainId = 0;
        AthenzDomain athenzDomain = new AthenzDomain(domainName);

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    athenzDomain.setDomain(saveDomainSettings(domainName, rs, true));
                    domainId = rs.getInt(JDBCConsts.DB_COLUMN_DOMAIN_ID);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }

        getAthenzDomainRoles(domainName, domainId, athenzDomain);
        getAthenzDomainGroups(domainName, domainId, athenzDomain);
        getAthenzDomainPolicies(domainName, domainId, athenzDomain);
        getAthenzDomainServices(domainName, domainId, athenzDomain);
        getAthenzDomainEntities(domainName, domainId, athenzDomain);

        return athenzDomain;
    }

    @Override
    public DomainMetaList listModifiedDomains(long modifiedSince) throws ServerResourceException {

        final String caller = "listModifiedDomains";

        DomainMetaList domainModifiedList = new DomainMetaList();
        List nameMods = new ArrayList<>();

        try (PreparedStatement ps = prepareDomainScanStatement(null, modifiedSince)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String domainName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    nameMods.add(saveDomainSettings(domainName, rs, false));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        domainModifiedList.setDomains(nameMods);
        return domainModifiedList;
    }

    boolean validatePrincipalDomain(String principal) {
        // special case for all principals
        if (ALL_PRINCIPALS.equals(principal)) {
            return true;
        }
        int idx = principal.indexOf(AuthorityConsts.GROUP_SEP);
        if (idx == -1) {
            idx = principal.lastIndexOf('.');
            if (idx == -1 || idx == 0 || idx == principal.length() - 1) {
                return false;
            }
        }
        return getDomainId(principal.substring(0, idx)) != 0;
    }

    String roleIndex(String domainId, String roleName) {
        return domainId + ':' + roleName;
    }

    PreparedStatement prepareRoleAssertionsStatement(String action)
            throws SQLException {

        PreparedStatement ps;
        if (!StringUtil.isEmpty(action)) {
            ps = con.prepareStatement(SQL_LIST_ROLE_ASSERTIONS + SQL_LIST_ROLE_ASSERTION_QUERY_ACTION);
            ps.setString(1, action);
        } else {
            ps = con.prepareStatement(SQL_LIST_ROLE_ASSERTIONS + SQL_LIST_ROLE_ASSERTION_NO_ACTION);
        }
        return ps;
    }

    Map> getRoleAssertions(String action, String caller) throws ServerResourceException {

        Map> roleAssertions = new HashMap<>();
        try (PreparedStatement ps = prepareRoleAssertionsStatement(action)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    Assertion assertion = new Assertion();
                    String domainName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    String roleName = rs.getString(JDBCConsts.DB_COLUMN_ROLE);
                    assertion.setRole(ResourceUtils.roleResourceName(domainName, roleName));
                    assertion.setResource(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE));
                    assertion.setAction(rs.getString(JDBCConsts.DB_COLUMN_ACTION));
                    assertion.setEffect(AssertionEffect.valueOf(rs.getString(JDBCConsts.DB_COLUMN_EFFECT)));
                    assertion.setId((long) rs.getInt(JDBCConsts.DB_COLUMN_ASSERT_ID));

                    String index = roleIndex(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_ID), roleName);
                    List assertions = roleAssertions.computeIfAbsent(index, k -> new ArrayList<>());

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{}: adding assertion {} for {}", caller, assertion, index);
                    }

                    assertions.add(assertion);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return roleAssertions;
    }

    Set getRolePrincipals(final String principalName, final String caller) throws ServerResourceException {

        // first let's find out all the roles that given principal is member of

        Set rolePrincipals = getRolesForPrincipal(principalName, caller);

        // next let's extract all groups that the given principal is member of
        // if the group list is not empty then we need to extract all the roles
        // where groups are member of and include those roles that match our
        // extracted groups in the role principals map

        Set groups = getGroupsForPrincipal(principalName, caller);
        if (!groups.isEmpty()) {
            updatePrincipalRoleGroupMembership(rolePrincipals, groups, principalName, caller);
        }
        return rolePrincipals;
    }

    void updatePrincipalRoleGroupMembership(Set rolePrincipals, final Set groups,
            final String principalName, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE_GROUP_PRINCIPALS)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {

                    final String groupName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    if (!groups.contains(groupName)) {
                        continue;
                    }

                    final String roleName = rs.getString(JDBCConsts.DB_COLUMN_ROLE_NAME);
                    final String index = roleIndex(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_ID), roleName);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{}: adding principal {} for {}", caller, principalName, index);
                    }

                    rolePrincipals.add(index);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    Set getGroupsForPrincipal(final String principalName, final String caller) throws ServerResourceException {

        Set groups = new HashSet<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_GROUP_FOR_PRINCIPAL)) {
            ps.setString(1, principalName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String groupName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    final String domainName = rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_NAME);
                    groups.add(ResourceUtils.groupResourceName(domainName, groupName));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return groups;
    }

    Set getRolesForPrincipal(final String principalName, final String caller) throws ServerResourceException {

        Set rolePrincipals = new HashSet<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLE_PRINCIPALS)) {
            ps.setString(1, principalName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {

                    final String roleName = rs.getString(JDBCConsts.DB_COLUMN_ROLE_NAME);
                    final String index = roleIndex(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_ID), roleName);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{}: adding principal {} for {}", caller, principalName, index);
                    }

                    rolePrincipals.add(index);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return rolePrincipals;
    }

    void getTrustedSubTypeRoles(String sqlCommand, Map> trustedRoles,
            String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(sqlCommand)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String trustDomainId = rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_ID);
                    String trustRoleName = rs.getString(JDBCConsts.DB_COLUMN_NAME);
                    String assertDomainId = rs.getString(JDBCConsts.DB_COLUMN_ASSERT_DOMAIN_ID);
                    String assertRoleName = rs.getString(JDBCConsts.DB_COLUMN_ROLE);

                    String index = roleIndex(assertDomainId, assertRoleName);
                    List roles = trustedRoles.computeIfAbsent(index, k -> new ArrayList<>());
                    String tRoleName = roleIndex(trustDomainId, trustRoleName);
                    roles.add(tRoleName);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    long lastTrustRoleUpdatesTimestamp() {

        final String caller = "lastTrustRoleUpdatesTimestamp";

        long timeStamp = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_LAST_ASSUME_ROLE_ASSERTION)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    timeStamp = rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime();
                }
            }
        } catch (SQLException ignored) {
        }

        return timeStamp;
    }

    Map> getTrustedRoles(String caller) throws ServerResourceException {

        // if our last timestamp has passed our timeout or our map has not been
        // initialized, then we need to update our trust map so need for any
        // extra timestamp checks

        long now = System.currentTimeMillis();
        if (SERVER_TRUST_ROLES_MAP == null || now - SERVER_TRUST_ROLES_TIMESTAMP > SERVER_TRUST_ROLES_UPDATE_TIMEOUT) {
            updateTrustRolesMap(now, true, caller);
        } else {

            // we want to make sure to capture any additions right away, so we'll get
            // the last modification timestamp of the latest policy that has an assume_role
            // assertion

            long lastTimeStamp = lastTrustRoleUpdatesTimestamp();
            if (lastTimeStamp > SERVER_TRUST_ROLES_TIMESTAMP) {
                updateTrustRolesMap(lastTimeStamp, false, caller);
            }
        }

        return SERVER_TRUST_ROLES_MAP;
    }

    void updateTrustRolesMap(long lastTimeStamp, boolean timeoutUpdate, final String caller) throws ServerResourceException {

        synchronized (synchronizer) {

            // a couple of simple checks in case we already have a valid
            // map to see if we can skip updating the map

            if (SERVER_TRUST_ROLES_MAP != null) {

                // if our last timestamp is older than the one we have
                // then we're going to skip the update

                if (SERVER_TRUST_ROLES_TIMESTAMP >= lastTimeStamp) {
                    return;
                }

                // if this is a timeout update we're going to check if the map
                // has already been updated by another thread while we were waiting

                if (timeoutUpdate && lastTimeStamp - SERVER_TRUST_ROLES_TIMESTAMP < SERVER_TRUST_ROLES_UPDATE_TIMEOUT) {
                    return;
                }
            }

            Map> trustedRoles = new HashMap<>();
            getTrustedSubTypeRoles(SQL_LIST_TRUSTED_STANDARD_ROLES, trustedRoles, caller);
            getTrustedSubTypeRoles(SQL_LIST_TRUSTED_WILDCARD_ROLES, trustedRoles, caller);
            SERVER_TRUST_ROLES_TIMESTAMP = lastTimeStamp;
            SERVER_TRUST_ROLES_MAP = trustedRoles;
        }
    }

    void addRoleAssertions(List principalAssertions, List roleAssertions) {
        if (roleAssertions != null && !roleAssertions.isEmpty()) {
            principalAssertions.addAll(roleAssertions);
        }
    }

    ResourceAccess getResourceAccessObject(String principal, List assertions) {
        ResourceAccess rsrcAccess = new ResourceAccess();
        rsrcAccess.setPrincipal(principal);
        rsrcAccess.setAssertions(assertions != null ? assertions : new ArrayList<>());
        return rsrcAccess;
    }

    @Override
    public ResourceAccessList listResourceAccess(String principal, String action, String userDomain) throws ServerResourceException {

        final String caller = "listResourceAccess";

        ResourceAccessList rsrcAccessList = new ResourceAccessList();
        List resources = new ArrayList<>();
        rsrcAccessList.setResources(resources);

        // first let's get the principal list that we're asked to check for
        // since if we have no matches then we have nothing to do

        Set rolePrincipals = getRolePrincipals(principal, caller);
        if (rolePrincipals.isEmpty()) {

            // so the given principal is not available as a role member
            // so before returning an empty response let's make sure
            // that it has been registered in Athenz otherwise we'll
            // just return 404 - not found exception

            if (getPrincipalId(principal) == 0) {
                throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
            }

            resources.add(getResourceAccessObject(principal, null));
            return rsrcAccessList;
        }

        // now let's get the list of role assertions. if we have
        // no matches, then we have nothing to do

        Map> roleAssertions = getRoleAssertions(action, caller);
        if (roleAssertions.isEmpty()) {
            resources.add(getResourceAccessObject(principal, null));
            return rsrcAccessList;
        }

        // finally we need to get all the trusted role maps

        Map> trustedRoles = getTrustedRoles(caller);

        // now let's go ahead and combine all of our data together
        // we're going to go through each principal, lookup
        // the assertions for the role and add them to the return object
        // if the role has no corresponding assertions, then we're going
        // to look at the trust role map in case it's a trusted role

        Map> principalAssertions = new HashMap<>();
        for (String roleIndex : rolePrincipals) {

            if (LOG.isDebugEnabled()) {
                LOG.debug("{}: processing role: {}", caller, roleIndex);
            }

            List assertions = principalAssertions.computeIfAbsent(principal, k -> new ArrayList<>());

            // retrieve the assertions for this role

            addRoleAssertions(assertions, roleAssertions.get(roleIndex));

            // check to see if this is a trusted role. There might be multiple
            // roles all being mapped as trusted, so we need to process them all

            List mappedTrustedRoles = trustedRoles.get(roleIndex);
            if (mappedTrustedRoles != null) {
                for (String mappedTrustedRole : mappedTrustedRoles) {

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{}: processing trusted role: {}", caller, mappedTrustedRole);
                    }

                    addRoleAssertions(assertions, roleAssertions.get(mappedTrustedRole));
                }
            }
        }

        // finally we need to create resource access list objects and return

        for (Map.Entry> entry : principalAssertions.entrySet()) {
            resources.add(getResourceAccessObject(entry.getKey(), entry.getValue()));
        }

        return rsrcAccessList;
    }

    @Override
    public Stats getStats(String domainName) throws ServerResourceException {

        final String caller = "getStats";

        if (!StringUtil.isEmpty(domainName)) {
            int domainId = getDomainId(domainName);
            if (domainId == 0) {
                throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
            }
            return getDomainStats(domainName, domainId);
        } else {
            return getSystemStats();
        }
    }

    Stats getSystemStats() throws ServerResourceException {
        Stats stats = new Stats();
        stats.setAssertion(getObjectSystemCount(SQL_TABLE_ASSERTION));
        stats.setRole(getObjectSystemCount(SQL_TABLE_ROLE));
        stats.setRoleMember(getObjectSystemCount(SQL_TABLE_ROLE_MEMBER));
        stats.setPolicy(getObjectSystemCount(SQL_TABLE_POLICY));
        stats.setService(getObjectSystemCount(SQL_TABLE_SERVICE));
        stats.setServiceHost(getObjectSystemCount(SQL_TABLE_SERVICE_HOST));
        stats.setPublicKey(getObjectSystemCount(SQL_TABLE_PUBLIC_KEY));
        stats.setEntity(getObjectSystemCount(SQL_TABLE_ENTITY));
        stats.setSubdomain(getObjectSystemCount(SQL_TABLE_DOMAIN));
        stats.setGroup(getObjectSystemCount(SQL_TABLE_PRINCIPAL_GROUP));
        stats.setGroupMember(getObjectSystemCount(SQL_TABLE_PRINCIPAL_GROUP_MEMBER));
        return stats;
    }

    Stats getDomainStats(final String domainName, int domainId) throws ServerResourceException {

        Stats stats = new Stats().setName(domainName);
        stats.setRole(getObjectDomainCount(SQL_TABLE_ROLE, domainId));
        stats.setPolicy(getObjectDomainCount(SQL_TABLE_POLICY, domainId));
        stats.setEntity(getObjectDomainCount(SQL_TABLE_ENTITY, domainId));
        stats.setService(getObjectDomainCount(SQL_TABLE_SERVICE, domainId));
        stats.setGroup(getObjectDomainCount(SQL_TABLE_PRINCIPAL_GROUP, domainId));

        stats.setAssertion(getObjectDomainComponentCount(SQL_GET_DOMAIN_ASSERTION_COUNT, domainId));
        stats.setRoleMember(getObjectDomainComponentCount(SQL_GET_DOMAIN_ROLE_MEMBER_COUNT, domainId));
        stats.setGroupMember(getObjectDomainComponentCount(SQL_GET_DOMAIN_GROUP_MEMBER_COUNT, domainId));
        stats.setServiceHost(getObjectDomainComponentCount(SQL_GET_DOMAIN_SERVICE_HOST_COUNT, domainId));
        stats.setPublicKey(getObjectDomainComponentCount(SQL_GET_DOMAIN_SERVICE_PUBLIC_KEY_COUNT, domainId));

        stats.setSubdomain(getSubdomainPrefixCount(domainName));
        return stats;
    }

    int getObjectSystemCount(final String tableName) throws ServerResourceException {

        final String caller = "getObjectSystemCount";

        int count = 0;
        final String sqlCommand = SQL_GET_OBJECT_SYSTEM_COUNT + tableName;
        try (PreparedStatement ps = con.prepareStatement(sqlCommand)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    int getObjectDomainCount(final String tableName, int domainId) throws ServerResourceException {

        final String caller = "getObjectDomainCount";

        int count = 0;
        final String sqlCommand = SQL_GET_OBJECT_DOMAIN_COUNT + tableName + SQL_GET_OBJECT_DOMAIN_COUNT_QUERY;
        try (PreparedStatement ps = con.prepareStatement(sqlCommand)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    int getObjectDomainComponentCount(final String sqlCommand, int domainId) throws ServerResourceException {

        final String caller = "getObjectDomainComponentCount";

        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(sqlCommand)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    int getSubdomainPrefixCount(final String domainName) throws ServerResourceException {

        final String caller = "getSubdomainPrefixCount";

        final String domainPrefix = domainName + ".";
        int len = domainPrefix.length();
        char c = (char) (domainPrefix.charAt(len - 1) + 1);
        final String stop = domainPrefix.substring(0, len - 1) + c;

        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_PREFIX_COUNT)) {
            ps.setString(1, domainPrefix);
            ps.setString(2, stop);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public Quota getQuota(String domainName) throws ServerResourceException {

        final String caller = "getQuota";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        Quota quota = null;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_QUOTA)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    quota = new Quota().setName(domainName);
                    quota.setAssertion(rs.getInt(JDBCConsts.DB_COLUMN_ASSERTION));
                    quota.setRole(rs.getInt(JDBCConsts.DB_COLUMN_ROLE));
                    quota.setRoleMember(rs.getInt(JDBCConsts.DB_COLUMN_ROLE_MEMBER));
                    quota.setPolicy(rs.getInt(JDBCConsts.DB_COLUMN_POLICY));
                    quota.setService(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE));
                    quota.setServiceHost(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_HOST));
                    quota.setPublicKey(rs.getInt(JDBCConsts.DB_COLUMN_PUBLIC_KEY));
                    quota.setEntity(rs.getInt(JDBCConsts.DB_COLUMN_ENTITY));
                    quota.setSubdomain(rs.getInt(JDBCConsts.DB_COLUMN_SUBDOMAIN));
                    quota.setGroup(rs.getInt(JDBCConsts.DB_COLUMN_PRINCIPAL_GROUP));
                    quota.setGroupMember(rs.getInt(JDBCConsts.DB_COLUMN_PRINCIPAL_GROUP_MEMBER));
                    quota.setModified(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime()));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return quota;
    }

    @Override
    public boolean insertQuota(String domainName, Quota quota) throws ServerResourceException {

        final String caller = "insertQuota";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_QUOTA)) {
            ps.setInt(1, domainId);
            ps.setInt(2, quota.getRole());
            ps.setInt(3, quota.getRoleMember());
            ps.setInt(4, quota.getPolicy());
            ps.setInt(5, quota.getAssertion());
            ps.setInt(6, quota.getService());
            ps.setInt(7, quota.getServiceHost());
            ps.setInt(8, quota.getPublicKey());
            ps.setInt(9, quota.getEntity());
            ps.setInt(10, quota.getSubdomain());
            ps.setInt(11, quota.getGroup());
            ps.setInt(12, quota.getGroupMember());
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateQuota(String domainName, Quota quota) throws ServerResourceException {

        final String caller = "updateQuota";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_QUOTA)) {
            ps.setInt(1, quota.getRole());
            ps.setInt(2, quota.getRoleMember());
            ps.setInt(3, quota.getPolicy());
            ps.setInt(4, quota.getAssertion());
            ps.setInt(5, quota.getService());
            ps.setInt(6, quota.getServiceHost());
            ps.setInt(7, quota.getPublicKey());
            ps.setInt(8, quota.getEntity());
            ps.setInt(9, quota.getSubdomain());
            ps.setInt(10, quota.getGroup());
            ps.setInt(11, quota.getGroupMember());
            ps.setInt(12, domainId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteQuota(String domainName) throws ServerResourceException {

        final String caller = "deleteQuota";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_QUOTA)) {
            ps.setInt(1, domainId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public DomainRoleMembers listDomainRoleMembers(String domainName) throws ServerResourceException {
        return listDomainRoleMembersWithQuery(domainName, SQL_GET_DOMAIN_ROLE_MEMBERS, "listDomainRoleMembers");
    }

    @Override
    public DomainRoleMember getPrincipalRoles(String principal, String domainName) throws ServerResourceException {

        final String caller = "getPrincipalRoles";

        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        DomainRoleMember roleMember = new DomainRoleMember();
        roleMember.setMemberRoles(new ArrayList<>());
        roleMember.setMemberName(principal);
        if (StringUtil.isEmpty(domainName)) {
            try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL_ROLES)) {
                ps.setInt(1, principalId);
                return getRolesForPrincipal(caller, roleMember, ps);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        } else {
            int domainId = getDomainId(domainName);
            if (domainId == 0) {
                throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
            }

            try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL_ROLES_DOMAIN)) {
                ps.setInt(1, principalId);
                ps.setInt(2, domainId);
                return getRolesForPrincipal(caller, roleMember, ps);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
    }

    private DomainRoleMember getRolesForPrincipal(String caller, DomainRoleMember roleMember, PreparedStatement ps) throws SQLException {
        try (ResultSet rs = executeQuery(ps, caller)) {
            while (rs.next()) {
                final String roleName = rs.getString(1);
                final String domain = rs.getString(2);

                MemberRole memberRole = new MemberRole();
                memberRole.setRoleName(roleName);
                memberRole.setDomainName(domain);

                java.sql.Timestamp expiration = rs.getTimestamp(3);
                if (expiration != null) {
                    memberRole.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                }
                java.sql.Timestamp reviewReminder = rs.getTimestamp(4);
                if (reviewReminder != null) {
                    memberRole.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                }
                memberRole.setSystemDisabled(nullIfDefaultValue(rs.getInt(5), 0));

                roleMember.getMemberRoles().add(memberRole);
            }

            return roleMember;
        }
    }

    @Override
    public DomainRoleMembers listOverdueReviewRoleMembers(String domainName) throws ServerResourceException {
        return listDomainRoleMembersWithQuery(domainName, SQL_GET_REVIEW_OVERDUE_DOMAIN_ROLE_MEMBERS, "listDomainRoleMembersWithQuery");
    }

    @Override
    public Map> getPendingDomainGroupMembersByPrincipal(String principal) throws ServerResourceException {

        final String caller = "getPendingDomainGroupMembersList";
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        Map> domainGroupMembersMap = new LinkedHashMap<>();

        // first we're going to retrieve all the members that are waiting
        // for approval based on their domain org values

        processPendingGroupMembers(JDBCConsts.SYS_AUTH_AUDIT_BY_ORG, SQL_PENDING_ORG_AUDIT_GROUP_MEMBER_LIST,
                principalId, domainGroupMembersMap, caller);

        // then we're going to retrieve all the members that are waiting
        // for approval based on their domain name values

        processPendingGroupMembers(JDBCConsts.SYS_AUTH_AUDIT_BY_DOMAIN, SQL_PENDING_DOMAIN_AUDIT_GROUP_MEMBER_LIST,
                principalId, domainGroupMembersMap, caller);

        // finally retrieve the self serve groups

        try (PreparedStatement ps = con.prepareStatement(SQL_PENDING_DOMAIN_ADMIN_GROUP_MEMBER_LIST)) {
            ps.setInt(1, principalId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    populateDomainGroupMembersMapFromResultSet(domainGroupMembersMap, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domainGroupMembersMap;
    }

    @Override
    public Map> getPendingDomainGroupMembersByDomain(String domainName) throws ServerResourceException {

        final String caller = "getPendingDomainGroupMembersList";
        final boolean allDomains = "*".equals(domainName);
        int domainId = 0;

        if (!allDomains) {
            domainId = getDomainId(domainName);
            if (domainId == 0) {
                throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
            }
        }

        Map> domainGroupMembersMap = new LinkedHashMap<>();
        if (allDomains) {
            // retrieve all members pending approval in principal group across all domains

            try (PreparedStatement ps = con.prepareStatement(SQL_PENDING_ALL_DOMAIN_GROUP_MEMBER_LIST)) {
                try (ResultSet rs = executeQuery(ps, caller)) {
                    while (rs.next()) {
                        populateDomainGroupMembersMapFromResultSet(domainGroupMembersMap, rs);
                    }
                }
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        } else {
            // retrieve all the members that are waiting for approval
            // in review enabled, self serve and audit enabled groups for given domain

            try (PreparedStatement ps = con.prepareStatement(SQL_PENDING_DOMAIN_GROUP_MEMBER_LIST)) {
                ps.setInt(1, domainId);
                try (ResultSet rs = executeQuery(ps, caller)) {
                    while (rs.next()) {
                        populateDomainGroupMembersMapFromResultSet(domainGroupMembersMap, rs);
                    }
                }
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }

        return domainGroupMembersMap;
    }

    @Override
    public Map> getExpiredPendingDomainGroupMembers(int pendingGroupMemberLifespan) throws ServerResourceException {

        final String caller = "getExpiredPendingDomainGroupMembers";

        //update audit log with details before deleting

        Map> domainGroupMembersMap = new LinkedHashMap<>();

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_EXPIRED_PENDING_GROUP_MEMBERS)) {
            ps.setInt(1, pendingGroupMemberLifespan);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    populateDomainGroupMembersMapFromResultSet(domainGroupMembersMap, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return domainGroupMembersMap;
    }

    @Override
    public Set getPendingGroupMembershipApproverRoles(String server, long timestamp) throws ServerResourceException {

        final String caller = "getPendingGroupMembershipApproverGroups";

        Set targetRoles = new HashSet<>();
        int orgDomainId = getDomainId(JDBCConsts.SYS_AUTH_AUDIT_BY_ORG);
        int domDomainId = getDomainId(JDBCConsts.SYS_AUTH_AUDIT_BY_DOMAIN);

        java.sql.Timestamp ts = new java.sql.Timestamp(timestamp);

        //Get orgs and domains for audit enabled groups with pending membership

        try (PreparedStatement ps = con.prepareStatement(SQL_AUDIT_ENABLED_PENDING_GROUP_MEMBERSHIP_REMINDER_ENTRIES)) {
            ps.setTimestamp(1, ts);
            ps.setString(2, server);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {

                    // first process the org value

                    final String org = rs.getString(1);
                    if (org != null && !org.isEmpty()) {
                        int roleId = getRoleId(orgDomainId, org);
                        if (roleId != 0) {
                            targetRoles.add(ResourceUtils.roleResourceName(JDBCConsts.SYS_AUTH_AUDIT_BY_ORG, org));
                        }
                    }

                    // then process the domain value

                    final String domain = rs.getString(2);
                    int roleId = getRoleId(domDomainId, domain);
                    if (roleId != 0) {
                        targetRoles.add(ResourceUtils.roleResourceName(JDBCConsts.SYS_AUTH_AUDIT_BY_DOMAIN, domain));
                    }
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // get admin groups of pending self-serve and review-enabled requests

        getRecipientRoleForAdminGroupMembershipApproval(caller, targetRoles, ts, server);

        return targetRoles;
    }

    @Override
    public boolean updatePendingGroupMembersNotificationTimestamp(String server, long timestamp, int delayDays) throws ServerResourceException {

        final String caller = "updatePendingGroupMembersNotificationTimestamp";
        int affectedRows;
        java.sql.Timestamp ts = new java.sql.Timestamp(timestamp);
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PENDING_GROUP_MEMBERS_NOTIFICATION_TIMESTAMP)) {
            ps.setTimestamp(1, ts);
            ps.setString(2, server);
            ps.setTimestamp(3, ts);
            ps.setInt(4, delayDays);

            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    private DomainRoleMembers listDomainRoleMembersWithQuery(String domainName, String query, String caller) throws ServerResourceException {
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        DomainRoleMembers domainRoleMembers = new DomainRoleMembers();
        domainRoleMembers.setDomainName(domainName);

        Map memberMap = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String roleName = rs.getString(1);
                    final String memberName = rs.getString(2);

                    DomainRoleMember domainRoleMember = memberMap.get(memberName);
                    if (domainRoleMember == null) {
                        domainRoleMember = new DomainRoleMember();
                        domainRoleMember.setMemberName(memberName);
                        memberMap.put(memberName, domainRoleMember);
                    }
                    List memberRoles = domainRoleMember.getMemberRoles();
                    if (memberRoles == null) {
                        memberRoles = new ArrayList<>();
                        domainRoleMember.setMemberRoles(memberRoles);
                    }
                    MemberRole memberRole = new MemberRole();
                    memberRole.setRoleName(roleName);

                    java.sql.Timestamp expiration = rs.getTimestamp(3);
                    if (expiration != null) {
                        memberRole.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    java.sql.Timestamp reviewReminder = rs.getTimestamp(4);
                    if (reviewReminder != null) {
                        memberRole.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                    }
                    memberRole.setSystemDisabled(nullIfDefaultValue(rs.getInt(5), 0));
                    memberRoles.add(memberRole);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        if (!memberMap.isEmpty()) {
            domainRoleMembers.setMembers(new ArrayList<>(memberMap.values()));
        }
        return domainRoleMembers;
    }

    @Override
    public boolean deletePendingRoleMember(String domainName, String roleName, String principal,
            String admin, String auditRef) throws ServerResourceException {

        final String caller = "deletePendingRoleMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }
        return executeDeletePendingRoleMember(roleId, principalId, admin, principal, auditRef, true, caller);
    }

    public boolean executeDeletePendingRoleMember(int roleId, int principalId, final String admin,
            final String principal, final String auditRef, boolean auditLog, final String caller) throws ServerResourceException {

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_PENDING_ROLE_MEMBER)) {
            ps.setInt(1, roleId);
            ps.setInt(2, principalId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        boolean result = (affectedRows > 0);
        if (result && auditLog) {
            result = insertRoleAuditLog(roleId, admin, principal, "REJECT", auditRef);
        }
        return result;
    }

    @Override
    public boolean confirmRoleMember(String domainName, String roleName, RoleMember roleMember,
            String admin, String auditRef) throws ServerResourceException {

        final String caller = "confirmRoleMember";

        String principal = roleMember.getMemberName();
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        // need to check if the pending entry already exists
        // before doing any work

        String state = getPendingRoleMemberState(roleId, principal);
        if (state == null) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        boolean result = false;
        if (roleMember.getApproved() == Boolean.TRUE) {
            if (JDBCConsts.PENDING_REQUEST_ADD_STATE.equals(state)) {
                boolean roleMemberExists = roleMemberExists(roleId, principalId, principal, null, caller);
                result = insertStandardRoleMember(roleId, principalId, roleMember, admin,
                        principal, auditRef, roleMemberExists, true, caller);
            } else if (JDBCConsts.PENDING_REQUEST_DELETE_STATE.equals(state)) {
                result = deleteRoleMember(domainName, roleName, principal, admin, auditRef);
            }
            if (result) {
                executeDeletePendingRoleMember(roleId, principalId, admin, principal,
                        auditRef, false, caller);
            }
        } else {
            result = executeDeletePendingRoleMember(roleId, principalId, admin,
                principal, auditRef, true, caller);
        }

        return result;
    }

    public String getPendingRoleMemberState(Integer roleId, String member) throws ServerResourceException {

        final String caller = "getPendingRoleMemberState";
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PENDING_ROLE_MEMBER_STATE)) {
            ps.setInt(1, roleId);
            ps.setString(2, member);
                try (ResultSet rs = executeQuery(ps, caller)) {
                    if (rs.next()) {
                        return rs.getString(1);
                    } else {
                        return null;
                    }
                }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

    }

    @Override
    public RoleMember getPendingRoleMember(String domainName, String roleName, String memberName) throws ServerResourceException {

        final String caller = "getPendingRoleMember";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PENDING_ROLE_MEMBER)) {
            ps.setInt(1, roleId);
            ps.setString(2, memberName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    RoleMember roleMember = new RoleMember();
                    roleMember.setMemberName(memberName);
                    java.sql.Timestamp expiration = rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION);
                    if (expiration != null) {
                        roleMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    java.sql.Timestamp reviewReminder = rs.getTimestamp(JDBCConsts.DB_COLUMN_REVIEW_REMINDER);
                    if (reviewReminder != null) {
                        roleMember.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                    }
                    roleMember.setRequestPrincipal(rs.getString(JDBCConsts.DB_COLUMN_REQ_PRINCIPAL));
                    roleMember.setPendingState(rs.getString(JDBCConsts.DB_COLUMN_PENDING_STATE));
                    return roleMember;
                } else {
                    return null;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    public String getPendingGroupMemberState(Integer groupId, String member) throws ServerResourceException {

        final String caller = "getPendingGroupMemberState";
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PENDING_GROUP_MEMBER_STATE)) {
            ps.setInt(1, groupId);
            ps.setString(2, member);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return rs.getString(1);
                } else {
                    return null;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

    }

    void processPendingMembers(final String domainName, final String query, int principalId,
            Map> domainRoleMembersMap, final String caller) throws ServerResourceException {

        int auditDomId = getDomainId(domainName);
        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setInt(1, principalId);
            ps.setInt(2, auditDomId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    populateDomainRoleMembersMapFromResultSet(domainRoleMembersMap, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    void processPendingGroupMembers(final String domainName, final String query, int principalId,
                                    Map> domainGroupMembersMap, final String caller) throws ServerResourceException {

        int auditDomId = getDomainId(domainName);
        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setInt(1, principalId);
            ps.setInt(2, auditDomId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    populateDomainGroupMembersMapFromResultSet(domainGroupMembersMap, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    @Override
    public Map> getPendingDomainRoleMembersByPrincipal(String principal) throws ServerResourceException {

        final String caller = "getPendingDomainRoleMembersList";
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        Map> domainRoleMembersMap = new LinkedHashMap<>();

        // first we're going to retrieve all the members that are waiting
        // for approval based on their domain org values

        processPendingMembers(JDBCConsts.SYS_AUTH_AUDIT_BY_ORG, SQL_PENDING_ORG_AUDIT_ROLE_MEMBER_LIST,
            principalId, domainRoleMembersMap, caller);

        // then we're going to retrieve all the members that are waiting
        // for approval based on their domain name values
        processPendingMembers(JDBCConsts.SYS_AUTH_AUDIT_BY_DOMAIN, SQL_PENDING_DOMAIN_AUDIT_ROLE_MEMBER_LIST,
            principalId, domainRoleMembersMap, caller);

        // finally retrieve the self serve roles
        try (PreparedStatement ps = con.prepareStatement(SQL_PENDING_DOMAIN_ADMIN_ROLE_MEMBER_LIST)) {
            ps.setInt(1, principalId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    populateDomainRoleMembersMapFromResultSet(domainRoleMembersMap, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return domainRoleMembersMap;
    }

    @Override
    public Map> getPendingDomainRoleMembersByDomain(String domainName) throws ServerResourceException {

        final String caller = "getPendingDomainRoleMembersList";
        final boolean allDomains = "*".equals(domainName);

        int domainId = 0;
        if (!allDomains) {
            domainId = getDomainId(domainName);
            if (domainId == 0) {
                throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
            }
        }

        Map> domainRoleMembersMap = new LinkedHashMap<>();

        if (allDomains) {
            // retrieve all the members waiting approval across all domains

            try (PreparedStatement ps = con.prepareStatement(SQL_PENDING_ALL_DOMAIN_ROLE_MEMBER_LIST)) {
                try (ResultSet rs = executeQuery(ps, caller)) {
                    while (rs.next()) {
                        populateDomainRoleMembersMapFromResultSet(domainRoleMembersMap, rs);
                    }
                }
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        } else {
            // retrieve all the members that are waiting for approval
            // in audit_enabled,review_enabled and self serve roles in given domain

            try (PreparedStatement ps = con.prepareStatement(SQL_PENDING_DOMAIN_ROLE_MEMBER_LIST)) {
                ps.setInt(1, domainId);
                try (ResultSet rs = executeQuery(ps, caller)) {
                    while (rs.next()) {
                        populateDomainRoleMembersMapFromResultSet(domainRoleMembersMap, rs);
                    }
                }
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }

        return domainRoleMembersMap;
    }

    private void populateDomainRoleMembersMapFromResultSet(Map> domainRoleMembersMap, ResultSet rs) throws SQLException {

        List domainRoleMembers;
        final String domain = rs.getString(1);
        if (!domainRoleMembersMap.containsKey(domain)) {
            domainRoleMembers = new ArrayList<>();
            domainRoleMembersMap.put(domain, domainRoleMembers);
        }
        domainRoleMembers = domainRoleMembersMap.get(domain);

        DomainRoleMember domainRoleMember = new DomainRoleMember();
        domainRoleMember.setMemberName(rs.getString(3));
        List memberRoles = new ArrayList<>();

        MemberRole memberRole = new MemberRole();
        memberRole.setRoleName(rs.getString(2));
        java.sql.Timestamp expiration = rs.getTimestamp(4);
        if (expiration != null) {
            memberRole.setExpiration(Timestamp.fromMillis(expiration.getTime()));
        }
        java.sql.Timestamp reviewReminder = rs.getTimestamp(5);
        if (reviewReminder != null) {
            memberRole.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
        }
        memberRole.setActive(false);
        memberRole.setAuditRef(rs.getString(6));

        expiration = rs.getTimestamp(7);
        if (expiration != null) {
            memberRole.setRequestTime(Timestamp.fromMillis(expiration.getTime()));
        }
        memberRole.setRequestPrincipal(rs.getString(8));
        memberRole.setPendingState(rs.getString(9));
        memberRoles.add(memberRole);
        domainRoleMember.setMemberRoles(memberRoles);
        if (!domainRoleMembers.contains(domainRoleMember)) {
            domainRoleMembers.add(domainRoleMember);
        }
    }

    private void populateDomainGroupMembersMapFromResultSet(Map> domainGroupMembersMap, ResultSet rs) throws SQLException {

        List domainGroupMembers;
        final String domain = rs.getString(1);
        if (!domainGroupMembersMap.containsKey(domain)) {
            domainGroupMembers = new ArrayList<>();
            domainGroupMembersMap.put(domain, domainGroupMembers);
        }
        domainGroupMembers = domainGroupMembersMap.get(domain);

        DomainGroupMember domainGroupMember = new DomainGroupMember();
        domainGroupMember.setMemberName(rs.getString(3));
        List memberGroups = new ArrayList<>();

        GroupMember memberGroup = new GroupMember();
        memberGroup.setGroupName(rs.getString(2));
        java.sql.Timestamp expiration = rs.getTimestamp(4);
        if (expiration != null) {
            memberGroup.setExpiration(Timestamp.fromMillis(expiration.getTime()));
        }
        memberGroup.setActive(false);
        memberGroup.setAuditRef(rs.getString(5));

        expiration = rs.getTimestamp(6);
        if (expiration != null) {
            memberGroup.setRequestTime(Timestamp.fromMillis(expiration.getTime()));
        }
        memberGroup.setRequestPrincipal(rs.getString(7));
        memberGroup.setPendingState(rs.getString(8));
        memberGroups.add(memberGroup);

        domainGroupMember.setMemberGroups(memberGroups);
        if (!domainGroupMembers.contains(domainGroupMember)) {
            domainGroupMembers.add(domainGroupMember);
        }
    }

    @Override
    public Set getPendingMembershipApproverRoles(String server, long timestamp) throws ServerResourceException {

        final String caller = "getPendingMembershipApproverRoles";

        Set targetRoles = new HashSet<>();
        int orgDomainId = getDomainId(JDBCConsts.SYS_AUTH_AUDIT_BY_ORG);
        int domDomainId = getDomainId(JDBCConsts.SYS_AUTH_AUDIT_BY_DOMAIN);

        java.sql.Timestamp ts = new java.sql.Timestamp(timestamp);

        //Get orgs and domains for audit enabled roles with pending membership

        try (PreparedStatement ps = con.prepareStatement(SQL_AUDIT_ENABLED_PENDING_MEMBERSHIP_REMINDER_ENTRIES)) {
            ps.setTimestamp(1, ts);
            ps.setString(2, server);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {

                    // first process the org value

                    final String org = rs.getString(1);
                    if (org != null && !org.isEmpty()) {
                        int roleId = getRoleId(orgDomainId, org);
                        if (roleId != 0) {
                            targetRoles.add(ResourceUtils.roleResourceName(JDBCConsts.SYS_AUTH_AUDIT_BY_ORG, org));
                        }
                    }

                    // then process the domain value

                    final String domain = rs.getString(2);
                    int roleId = getRoleId(domDomainId, domain);
                    if (roleId != 0) {
                        targetRoles.add(ResourceUtils.roleResourceName(JDBCConsts.SYS_AUTH_AUDIT_BY_DOMAIN, domain));
                    }
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        // get admin roles of pending self-serve and review-enabled requests

        getRecipientRoleForAdminMembershipApproval(caller, targetRoles, ts, server);

        return targetRoles;
    }

    @Override
    public Map> getExpiredPendingDomainRoleMembers(int pendingRoleMemberLifespan) throws ServerResourceException {

        final String caller = "getExpiredPendingMembers";
        //update audit log with details before deleting

        Map> domainRoleMembersMap = new LinkedHashMap<>();

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_EXPIRED_PENDING_ROLE_MEMBERS)) {
            ps.setInt(1, pendingRoleMemberLifespan);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    populateDomainRoleMembersMapFromResultSet(domainRoleMembersMap, rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return domainRoleMembersMap;
    }

    @Override
    public boolean updatePendingRoleMembersNotificationTimestamp(String server, long timestamp, int delayDays) throws ServerResourceException {
        final String caller = "updatePendingRoleMembersNotificationTimestamp";
        int affectedRows;
        java.sql.Timestamp ts = new java.sql.Timestamp(timestamp);
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PENDING_ROLE_MEMBERS_NOTIFICATION_TIMESTAMP)) {
            ps.setTimestamp(1, ts);
            ps.setString(2, server);
            ps.setTimestamp(3, ts);
            ps.setInt(4, delayDays);

            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    private void getRecipientRoleForAdminMembershipApproval(String caller, Set targetRoles,
                java.sql.Timestamp timestamp, String server) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_ADMIN_PENDING_MEMBERSHIP_REMINDER_DOMAINS)) {
            ps.setTimestamp(1, timestamp);
            ps.setString(2, server);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    targetRoles.add(ResourceUtils.roleResourceName(rs.getString(1), JDBCConsts.ADMIN_ROLE_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    private void getRecipientRoleForAdminGroupMembershipApproval(String caller, Set targetRoles,
                                                            java.sql.Timestamp timestamp, String server) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_ADMIN_PENDING_GROUP_MEMBERSHIP_REMINDER_DOMAINS)) {
            ps.setTimestamp(1, timestamp);
            ps.setString(2, server);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    targetRoles.add(ResourceUtils.roleResourceName(rs.getString(1), JDBCConsts.ADMIN_ROLE_NAME));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    @Override
    public Map getNotifyTemporaryRoleMembers(String server, long timestamp) throws ServerResourceException {
        return getNotifyRoleMembers(server, timestamp, SQL_LIST_NOTIFY_TEMPORARY_ROLE_MEMBERS, "listNotifyTemporaryRoleMembers");
    }

    @Override
    public boolean updateRoleMemberExpirationNotificationTimestamp(String server, long timestamp, int delayDays) throws ServerResourceException {

        // first verify that we haven't had any updates in the last configured
        // number of delayed days. We don't want multiple instances running
        // and generating multiple emails depending on the time. We want to
        // make sure, for example, to generate only a single email per day

        if (isLastNotifyTimeWithinSpecifiedDays(SQL_ROLE_EXPIRY_LAST_NOTIFIED_TIME, delayDays)) {
            return false;
        }

        // process our request

        return updateMemberNotificationTimestamp(server, timestamp,
                SQL_UPDATE_ROLE_MEMBERS_EXPIRY_NOTIFICATION_TIMESTAMP, "updateRoleMemberExpirationNotificationTimestamp");
    }

    @Override
    public Map getNotifyTemporaryGroupMembers(String server, long timestamp) throws ServerResourceException {

        final String caller = "getNotifyTemporaryGroupMembers";
        Map memberMap = new HashMap<>();

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_NOTIFY_TEMPORARY_GROUP_MEMBERS)) {
            ps.setTimestamp(1, new java.sql.Timestamp(timestamp));
            ps.setString(2, server);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String memberName = rs.getString(JDBCConsts.DB_COLUMN_PRINCIPAL_NAME);
                    java.sql.Timestamp expiration = rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION);

                    DomainGroupMember domainGroupMember = memberMap.get(memberName);
                    if (domainGroupMember == null) {
                        domainGroupMember = new DomainGroupMember();
                        domainGroupMember.setMemberName(memberName);
                        memberMap.put(memberName, domainGroupMember);
                    }
                    List memberGroups = domainGroupMember.getMemberGroups();
                    if (memberGroups == null) {
                        memberGroups = new ArrayList<>();
                        domainGroupMember.setMemberGroups(memberGroups);
                    }
                    GroupMember memberGroup = new GroupMember();
                    memberGroup.setMemberName(memberName);
                    memberGroup.setGroupName(rs.getString(JDBCConsts.DB_COLUMN_AS_GROUP_NAME));
                    memberGroup.setDomainName(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_NAME));
                    memberGroup.setNotifyRoles(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_ROLES)));
                    memberGroup.setNotifyDetails(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_DETAILS)));
                    if (expiration != null) {
                        memberGroup.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    memberGroups.add(memberGroup);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return memberMap;
    }

    @Override
    public boolean updateGroupMemberExpirationNotificationTimestamp(String server, long timestamp, int delayDays) throws ServerResourceException {

        // first verify that we haven't had any updates in the last configured
        // number of delayed days. We don't want multiple instances running
        // and generating multiple emails depending on the time. We want to
        // make sure, for example, to generate only a single email per day

        if (isLastNotifyTimeWithinSpecifiedDays(SQL_GROUP_EXPIRY_LAST_NOTIFIED_TIME, delayDays)) {
            return false;
        }

        return updateMemberNotificationTimestamp(server, timestamp,
                SQL_UPDATE_GROUP_MEMBERS_EXPIRY_NOTIFICATION_TIMESTAMP, "updateGroupMemberExpirationNotificationTimestamp");
    }

    @Override
    public Map getNotifyReviewRoleMembers(String server, long timestamp) throws ServerResourceException {
        return getNotifyRoleMembers(server, timestamp, SQL_LIST_NOTIFY_REVIEW_ROLE_MEMBERS, "listNotifyReviewRoleMembers");
    }

    @Override
    public boolean updateRoleMemberReviewNotificationTimestamp(String server, long timestamp, int delayDays) throws ServerResourceException {

        // first verify that we haven't had any updates in the last configured
        // number of delayed days. We don't want multiple instances running
        // and generating multiple emails depending on the time. We want to
        // make sure, for example, to generate only a single email per day

        if (isLastNotifyTimeWithinSpecifiedDays(SQL_ROLE_REVIEW_LAST_NOTIFIED_TIME, delayDays)) {
            return false;
        }

        return updateMemberNotificationTimestamp(server, timestamp,
                SQL_UPDATE_ROLE_MEMBERS_REVIEW_NOTIFICATION_TIMESTAMP, "updateRoleMemberReviewNotificationTimestamp");
    }

    private boolean updateMemberNotificationTimestamp(final String server, long timestamp, final String query, final String caller) throws ServerResourceException {
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setTimestamp(1, new java.sql.Timestamp(timestamp));
            ps.setString(2, server);

            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    private Map getNotifyRoleMembers(final String server, long timestamp, final String query,
                                                               final String caller) throws ServerResourceException {

        Map memberMap = new HashMap<>();

        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setTimestamp(1, new java.sql.Timestamp(timestamp));
            ps.setString(2, server);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String memberName = rs.getString(JDBCConsts.DB_COLUMN_PRINCIPAL_NAME);
                    java.sql.Timestamp expiration = rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION);
                    java.sql.Timestamp reviewReminder = rs.getTimestamp(JDBCConsts.DB_COLUMN_REVIEW_REMINDER);

                    DomainRoleMember domainRoleMember = memberMap.get(memberName);
                    if (domainRoleMember == null) {
                        domainRoleMember = new DomainRoleMember();
                        domainRoleMember.setMemberName(memberName);
                        memberMap.put(memberName, domainRoleMember);
                    }
                    List memberRoles = domainRoleMember.getMemberRoles();
                    if (memberRoles == null) {
                        memberRoles = new ArrayList<>();
                        domainRoleMember.setMemberRoles(memberRoles);
                    }
                    MemberRole memberRole = new MemberRole();
                    memberRole.setMemberName(memberName);
                    memberRole.setRoleName(rs.getString(JDBCConsts.DB_COLUMN_ROLE_NAME));
                    memberRole.setDomainName(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_NAME));
                    memberRole.setNotifyRoles(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_ROLES)));
                    memberRole.setNotifyDetails(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_DETAILS)));
                    if (expiration != null) {
                        memberRole.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    if (reviewReminder != null) {
                        memberRole.setReviewReminder(Timestamp.fromMillis(reviewReminder.getTime()));
                    }
                    memberRoles.add(memberRole);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return memberMap;
    }

    @Override
    public List getDomainTemplates(String domainName) throws ServerResourceException {
        TemplateMetaData templateDomainMapping;
        List templateDomainMappingList = new ArrayList<>();
        final String caller = "getDomainTemplates";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_TEMPLATES)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    templateDomainMapping = new TemplateMetaData();
                    templateDomainMapping.setTemplateName(rs.getString(JDBCConsts.DB_COLUMN_TEMPLATE_NAME));
                    templateDomainMapping.setCurrentVersion(rs.getInt(JDBCConsts.DB_COLUMN_TEMPLATE_VERSION));
                    templateDomainMappingList.add(templateDomainMapping);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return templateDomainMappingList;
    }

    @Override
    public List listRolesWithUserAuthorityRestrictions() throws ServerResourceException {

        final String caller = "listRolesWithUserAuthorityRestrictions";
        List roles = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_ROLES_WITH_RESTRICTIONS)) {

            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    PrincipalRole prRole = new PrincipalRole();
                    prRole.setDomainName(rs.getString(JDBCConsts.DB_COLUMN_AS_DOMAIN_NAME));
                    prRole.setRoleName(rs.getString(JDBCConsts.DB_COLUMN_AS_ROLE_NAME));
                    prRole.setDomainUserAuthorityFilter(rs.getString(JDBCConsts.DB_COLUMN_AS_DOMAIN_USER_AUTHORITY_FILTER));
                    prRole.setDomainMemberExpiryDays(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_EXPIRY_DAYS));
                    roles.add(prRole);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return roles;
    }

    Group retrieveGroup(ResultSet rs, final String domainName, final String groupName) throws SQLException {
        Group group = new Group().setName(ResourceUtils.groupResourceName(domainName, groupName))
                .setModified(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime()))
                .setAuditEnabled(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_AUDIT_ENABLED), false))
                .setSelfServe(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_SELF_SERVE), false))
                .setReviewEnabled(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_REVIEW_ENABLED), false))
                .setNotifyRoles(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_ROLES)))
                .setNotifyDetails(saveValue(rs.getString(JDBCConsts.DB_COLUMN_NOTIFY_DETAILS)))
                .setUserAuthorityFilter(saveValue(rs.getString(JDBCConsts.DB_COLUMN_USER_AUTHORITY_FILTER)))
                .setUserAuthorityExpiration(saveValue(rs.getString(JDBCConsts.DB_COLUMN_USER_AUTHORITY_EXPIRATION)))
                .setMemberExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_EXPIRY_DAYS), 0))
                .setServiceExpiryDays(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_EXPIRY_DAYS), 0))
                .setDeleteProtection(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_DELETE_PROTECTION), false))
                .setMaxMembers(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_MAX_MEMBERS), 0))
                .setSelfRenew(nullIfDefaultValue(rs.getBoolean(JDBCConsts.DB_COLUMN_SELF_RENEW), false))
                .setSelfRenewMins(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SELF_RENEW_MINS), 0))
                .setResourceOwnership(ResourceOwnership.getResourceGroupOwnership(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE_OWNER)))
                .setPrincipalDomainFilter(saveValue(rs.getString(JDBCConsts.DB_COLUMN_PRINCIPAL_DOMAIN_FILTER)));
        java.sql.Timestamp lastReviewedTime = rs.getTimestamp(JDBCConsts.DB_COLUMN_LAST_REVIEWED_TIME);
        if (lastReviewedTime != null) {
            group.setLastReviewedDate(Timestamp.fromMillis(lastReviewedTime.getTime()));
        }
        return group;
    }

    @Override
    public Group getGroup(String domainName, String groupName) throws ServerResourceException {
        final String caller = "getGroup";

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_GROUP)) {
            ps.setString(1, domainName);
            ps.setString(2, groupName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return retrieveGroup(rs, domainName, groupName);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    @Override
    public boolean insertGroup(String domainName, Group group) throws ServerResourceException {
        int affectedRows;
        final String caller = "insertGroup";

        String groupName = Utils.extractGroupName(domainName, group.getName());
        if (groupName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " insert group name: " + group.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }

        java.sql.Timestamp lastReviewedTime = group.getLastReviewedDate() == null ? null :
                new java.sql.Timestamp(group.getLastReviewedDate().millis());

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_GROUP)) {
            ps.setString(1, groupName);
            ps.setInt(2, domainId);
            ps.setBoolean(3, processInsertValue(group.getAuditEnabled(), false));
            ps.setBoolean(4, processInsertValue(group.getSelfServe(), false));
            ps.setBoolean(5, processInsertValue(group.getReviewEnabled(), false));
            ps.setString(6, processInsertValue(group.getNotifyRoles()));
            ps.setString(7, processInsertValue(group.getUserAuthorityFilter()));
            ps.setString(8, processInsertValue(group.getUserAuthorityExpiration()));
            ps.setInt(9, processInsertValue(group.getMemberExpiryDays()));
            ps.setInt(10, processInsertValue(group.getServiceExpiryDays()));
            ps.setBoolean(11, processInsertValue(group.getDeleteProtection(), false));
            ps.setTimestamp(12, lastReviewedTime);
            ps.setInt(13, processInsertValue(group.getMaxMembers()));
            ps.setBoolean(14, processInsertValue(group.getSelfRenew(), false));
            ps.setInt(15, processInsertValue(group.getSelfRenewMins()));
            ps.setString(16, processInsertValue(group.getPrincipalDomainFilter()));
            ps.setString(17, processInsertValue(group.getNotifyDetails()));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateGroup(String domainName, Group group) throws ServerResourceException {
        int affectedRows;
        final String caller = "updateGroup";

        String groupName = Utils.extractGroupName(domainName, group.getName());
        if (groupName == null) {
            throw requestError(caller, "domain name mismatch: " + domainName +
                    " update group name: " + group.getName());
        }

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }

        java.sql.Timestamp lastReviewedTime = group.getLastReviewedDate() == null ? null :
                new java.sql.Timestamp(group.getLastReviewedDate().millis());

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_GROUP)) {
            ps.setBoolean(1, processInsertValue(group.getAuditEnabled(), false));
            ps.setBoolean(2, processInsertValue(group.getSelfServe(), false));
            ps.setBoolean(3, processInsertValue(group.getReviewEnabled(), false));
            ps.setString(4, processInsertValue(group.getNotifyRoles()));
            ps.setString(5, processInsertValue(group.getUserAuthorityFilter()));
            ps.setString(6, processInsertValue(group.getUserAuthorityExpiration()));
            ps.setInt(7, processInsertValue(group.getMemberExpiryDays()));
            ps.setInt(8, processInsertValue(group.getServiceExpiryDays()));
            ps.setBoolean(9, processInsertValue(group.getDeleteProtection(), false));
            ps.setTimestamp(10, lastReviewedTime);
            ps.setInt(11, processInsertValue(group.getMaxMembers()));
            ps.setBoolean(12, processInsertValue(group.getSelfRenew(), false));
            ps.setInt(13, processInsertValue(group.getSelfRenewMins()));
            ps.setString(14, processInsertValue(group.getPrincipalDomainFilter()));
            ps.setString(15, processInsertValue(group.getNotifyDetails()));
            ps.setInt(16, groupId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return (affectedRows > 0);
    }

    @Override
    public boolean deleteGroup(String domainName, String groupName) throws ServerResourceException {

        final String caller = "deleteGroup";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_GROUP)) {
            ps.setInt(1, domainId);
            ps.setString(2, groupName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateGroupModTimestamp(String domainName, String groupName) throws ServerResourceException {
        int affectedRows;
        final String caller = "updateGroupModTimestamp";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_GROUP_MOD_TIMESTAMP)) {
            ps.setInt(1, groupId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public int countGroups(String domainName) throws ServerResourceException {
        final String caller = "countGroups";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_GROUP)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public List listGroupAuditLogs(String domainName, String groupName) throws ServerResourceException {

        final String caller = "listGroupAuditLogs";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        List logs = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_GROUP_AUDIT_LOGS)) {
            ps.setInt(1, groupId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    GroupAuditLog log = new GroupAuditLog();
                    log.setAction(rs.getString(JDBCConsts.DB_COLUMN_ACTION));
                    log.setMember(rs.getString(JDBCConsts.DB_COLUMN_MEMBER));
                    log.setAdmin(rs.getString(JDBCConsts.DB_COLUMN_ADMIN));
                    log.setAuditRef(saveValue(rs.getString(JDBCConsts.DB_COLUMN_AUDIT_REF)));
                    log.setCreated(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_CREATED).getTime()));
                    logs.add(log);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return logs;
    }

    @Override
    public boolean updateGroupReviewTimestamp(String domainName, String groupName) throws ServerResourceException {
        int affectedRows;
        final String caller = "updateGroupReviewTimestamp";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_GROUP_REVIEW_TIMESTAMP)) {
            ps.setInt(1, groupId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    void getStdGroupMembers(int groupId, List members, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_GROUP_MEMBERS)) {
            ps.setInt(1, groupId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    GroupMember groupMember = new GroupMember();
                    groupMember.setMemberName(rs.getString(1));
                    java.sql.Timestamp expiration = rs.getTimestamp(2);
                    if (expiration != null) {
                        groupMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    groupMember.setActive(nullIfDefaultValue(rs.getBoolean(3), true));
                    groupMember.setAuditRef(rs.getString(4));
                    groupMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(5), 0));
                    groupMember.setApproved(true);
                    members.add(groupMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    void getPendingGroupMembers(int groupId, List members, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_PENDING_GROUP_MEMBERS)) {
            ps.setInt(1, groupId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    GroupMember groupMember = new GroupMember();
                    groupMember.setMemberName(rs.getString(1));
                    java.sql.Timestamp timestamp = rs.getTimestamp(2);
                    if (timestamp != null) {
                        groupMember.setExpiration(Timestamp.fromMillis(timestamp.getTime()));
                    }
                    timestamp = rs.getTimestamp(3);
                    if (timestamp != null) {
                        groupMember.setRequestTime(Timestamp.fromMillis(timestamp.getTime()));
                    }
                    groupMember.setAuditRef(rs.getString(4));
                    groupMember.setPendingState(rs.getString(5));
                    groupMember.setActive(false);
                    groupMember.setApproved(false);
                    members.add(groupMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    @Override
    public List listGroupMembers(String domainName, String groupName, Boolean pending) throws ServerResourceException {
        final String caller = "listGroupMembers";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }

        // first get our standard group members

        List members = new ArrayList<>();
        getStdGroupMembers(groupId, members, caller);

        // if requested, include pending members as well

        if (pending == Boolean.TRUE) {
            getPendingGroupMembers(groupId, members, caller);
        }

        members.sort(GroupMemberComparator);
        return members;
    }

    @Override
    public int countGroupMembers(String domainName, String groupName) throws ServerResourceException {
        final String caller = "countGroupMembers";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_GROUP_MEMBERS)) {
            ps.setInt(1, groupId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    boolean getGroupMembership(final String query, int groupId, final String member, long expiration,
                               GroupMembership membership, boolean disabledFlagCheck, final String caller) throws ServerResourceException {

        try (PreparedStatement ps = con.prepareStatement(query)) {
            ps.setInt(1, groupId);
            ps.setString(2, member);
            if (expiration != 0) {
                ps.setTimestamp(3, new java.sql.Timestamp(expiration));
            }
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    membership.setIsMember(true);
                    java.sql.Timestamp expiry = rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION);
                    if (expiry != null) {
                        membership.setExpiration(Timestamp.fromMillis(expiry.getTime()));
                    }
                    membership.setRequestPrincipal(rs.getString(JDBCConsts.DB_COLUMN_REQ_PRINCIPAL));
                    if (disabledFlagCheck) {
                        membership.setSystemDisabled(nullIfDefaultValue(rs.getInt(JDBCConsts.DB_COLUMN_SYSTEM_DISABLED), 0));
                    } else {
                        membership.setPendingState(rs.getString(JDBCConsts.DB_COLUMN_PENDING_STATE));
                    }
                    return true;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return false;
    }

    @Override
    public GroupMembership getGroupMember(String domainName, String groupName, String member, long expiration, boolean pending) throws ServerResourceException {

        final String caller = "getGroupMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }

        GroupMembership membership = new GroupMembership()
                .setMemberName(member)
                .setGroupName(ResourceUtils.groupResourceName(domainName, groupName))
                .setIsMember(false);

        // first we're going to check if we have a standard user with the given
        // details before checking for pending unless we're specifically asking
        // for pending member only in which case we'll skip the first check

        if (!pending) {
            String query = expiration == 0 ? SQL_GET_GROUP_MEMBER : SQL_GET_TEMP_GROUP_MEMBER;
            if (getGroupMembership(query, groupId, member, expiration, membership, true, caller)) {
                membership.setApproved(true);
            }
        }

        if (!membership.getIsMember()) {
            String query = expiration == 0 ? SQL_GET_PENDING_GROUP_MEMBER : SQL_GET_TEMP_PENDING_GROUP_MEMBER;
            if (getGroupMembership(query, groupId, member, expiration, membership, false, caller)) {
                membership.setApproved(false);
            }
        }

        return membership;
    }

    boolean groupMemberExists(int groupId, int principalId, String principal, String pendingState, final String caller) throws ServerResourceException {

        boolean pending = pendingState != null;
        String statement = pending ? SQL_PENDING_GROUP_MEMBER_EXISTS : SQL_STD_GROUP_MEMBER_EXISTS;
        try (PreparedStatement ps = con.prepareStatement(statement)) {
            ps.setInt(1, groupId);
            ps.setInt(2, principalId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    if (pending) {
                        String currentState = rs.getString(1);
                        // check current request doesn't contradict the existing one
                        if (currentState != null && !currentState.equals(pendingState)) {
                            throw Utils.requestError("The user " + principal + " already has a pending request in a different state", caller);
                        }
                    }
                    return true;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return false;
    }

    boolean insertGroupAuditLog(int groupId, String admin, String member,
                               String action, String auditRef) throws ServerResourceException {

        int affectedRows;
        final String caller = "insertGroupAuditEntry";

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_GROUP_AUDIT_LOG)) {
            ps.setInt(1, groupId);
            ps.setString(2, processInsertValue(admin));
            ps.setString(3, member);
            ps.setString(4, action);
            ps.setString(5, processInsertValue(auditRef));
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    boolean insertPendingGroupMember(int groupId, int principalId, GroupMember groupMember,
                                    final String admin, final String auditRef, boolean groupMemberExists, final String caller) throws ServerResourceException {

        java.sql.Timestamp expiration = groupMember.getExpiration() == null ? null
                : new java.sql.Timestamp(groupMember.getExpiration().millis());

        int affectedRows;
        if (groupMemberExists) {

            try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PENDING_GROUP_MEMBER)) {
                ps.setTimestamp(1, expiration);
                ps.setString(2, processInsertValue(auditRef));
                ps.setString(3, processInsertValue(admin));
                ps.setInt(4, groupId);
                ps.setInt(5, principalId);
                affectedRows = executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }

        } else {

            try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_PENDING_GROUP_MEMBER)) {
                ps.setInt(1, groupId);
                ps.setInt(2, principalId);
                ps.setTimestamp(3, expiration);
                ps.setString(4, processInsertValue(auditRef));
                ps.setString(5, processInsertValue(admin));
                ps.setString(6, processInsertValue(groupMember.getPendingState()));
                affectedRows = executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }

        return (affectedRows > 0);
    }

    boolean insertStandardGroupMember(int groupId, int principalId, GroupMember groupMember,
                                     final String admin, final String principal, final String auditRef,
                                     boolean groupMemberExists, boolean approveRequest, final String caller) throws ServerResourceException {

        java.sql.Timestamp expiration = groupMember.getExpiration() == null ? null
                : new java.sql.Timestamp(groupMember.getExpiration().millis());

        boolean result;
        String auditOperation;

        if (groupMemberExists) {

            try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_GROUP_MEMBER)) {
                ps.setTimestamp(1, expiration);
                ps.setBoolean(2, processInsertValue(groupMember.getActive(), true));
                ps.setString(3, processInsertValue(auditRef));
                ps.setString(4, processInsertValue(admin));
                ps.setInt(5, groupId);
                ps.setInt(6, principalId);
                executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
            auditOperation = approveRequest ? AUDIT_OPERATION_APPROVE : AUDIT_OPERATION_UPDATE;
            result = true;

        } else {

            int affectedRows;
            try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_GROUP_MEMBER)) {
                ps.setInt(1, groupId);
                ps.setInt(2, principalId);
                ps.setTimestamp(3, expiration);
                ps.setBoolean(4, processInsertValue(groupMember.getActive(), true));
                ps.setString(5, processInsertValue(auditRef));
                ps.setString(6, processInsertValue(admin));
                affectedRows = executeUpdate(ps, caller);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }

            auditOperation = approveRequest ? AUDIT_OPERATION_APPROVE : AUDIT_OPERATION_ADD;
            result = (affectedRows > 0);
        }

        // add audit log entry for this change if the operation was successful
        // add return the result of the audit log insert operation

        if (result) {
            result = insertGroupAuditLog(groupId, admin, principal, auditOperation, auditRef);
        }
        return result;
    }

    @Override
    public boolean insertGroupMember(String domainName, String groupName, GroupMember groupMember,
                                     String admin, String auditRef) throws ServerResourceException {

        final String caller = "insertGroupMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        String principal = groupMember.getMemberName();
        if (!validatePrincipalDomain(principal)) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, principal);
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            principalId = insertPrincipal(principal);
            if (principalId == 0) {
                throw internalServerError(caller, "Unable to insert principal: " + principal);
            }
        }

        // need to check if entry already exists

        boolean pendingRequest = (groupMember.getApproved() == Boolean.FALSE);
        boolean groupMemberExists = groupMemberExists(groupId, principalId, principal, groupMember.getPendingState(), caller);

        // process the request based on the type of the request
        // either pending request or standard insert

        boolean result;
        if (pendingRequest) {
            result = insertPendingGroupMember(groupId, principalId, groupMember, admin,
                    auditRef, groupMemberExists, caller);
        } else {
            result = insertStandardGroupMember(groupId, principalId, groupMember, admin,
                    principal, auditRef, groupMemberExists, false, caller);
        }
        return result;
    }

    @Override
    public boolean deleteGroupMember(String domainName, String groupName, String principal, String admin, String auditRef) throws ServerResourceException {

        final String caller = "deleteGroupMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_GROUP_MEMBER)) {
            ps.setInt(1, groupId);
            ps.setInt(2, principalId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        // add audit log entry for this change if the delete was successful
        // add return the result of the audit log insert operation

        if (result) {
            result = insertGroupAuditLog(groupId, admin, principal, "DELETE", auditRef);
        }

        return result;
    }

    @Override
    public boolean deleteExpiredGroupMember(String domainName, String groupName, String principal, String admin, Timestamp expiration, String auditRef) throws ServerResourceException {

        final String caller = "deleteGroupMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }
        java.sql.Timestamp ts = new java.sql.Timestamp(expiration.millis());
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_EXPIRED_GROUP_MEMBER)) {
            ps.setInt(1, groupId);
            ps.setInt(2, principalId);
            ps.setTimestamp(3, ts);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        // add audit log entry for this change if the delete was successful
        // add return the result of the audit log insert operation

        if (result) {
            result = insertGroupAuditLog(groupId, admin, principal, "DELETE", auditRef);
        }

        return result;    }

    @Override
    public boolean updateGroupMemberDisabledState(String domainName, String groupName, String principal, String admin,
                                                  int disabledState, String auditRef) throws ServerResourceException {

        final String caller = "updateGroupMemberDisabledState";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_GROUP_MEMBER_DISABLED_STATE)) {
            ps.setInt(1, disabledState);
            ps.setString(2, processInsertValue(auditRef));
            ps.setString(3, processInsertValue(admin));
            ps.setInt(4, groupId);
            ps.setInt(5, principalId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        boolean result = (affectedRows > 0);

        // add audit log entry for this change if the disable was successful
        // add return the result of the audit log insert operation

        if (result) {
            final String operation = disabledState == 0 ? "ENABLE" : "DISABLE";
            result = insertGroupAuditLog(groupId, admin, principal, operation, auditRef);
        }

        return result;
    }

    @Override
    public boolean deletePendingGroupMember(String domainName, String groupName, String principal,
                                           String admin, String auditRef) throws ServerResourceException {

        final String caller = "deletePendingGroupMember";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }
        return executeDeletePendingGroupMember(groupId, principalId, admin, principal, auditRef, true, caller);
    }

    public boolean executeDeletePendingGroupMember(int groupId, int principalId, final String admin,
                                                  final String principal, final String auditRef, boolean auditLog, final String caller) throws ServerResourceException {

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_PENDING_GROUP_MEMBER)) {
            ps.setInt(1, groupId);
            ps.setInt(2, principalId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        boolean result = (affectedRows > 0);
        if (result && auditLog) {
            result = insertGroupAuditLog(groupId, admin, principal, "REJECT", auditRef);
        }
        return result;
    }

    @Override
    public boolean confirmGroupMember(String domainName, String groupName, GroupMember groupMember,
                                      String admin, String auditRef) throws ServerResourceException {

        final String caller = "confirmGroupMember";

        String principal = groupMember.getMemberName();
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        // need to check if the pending entry already exists
        // before doing any work

        String state = getPendingGroupMemberState(groupId, principal);
        if (state == null) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        boolean result = false;
        if (groupMember.getApproved() == Boolean.TRUE) {
            if (JDBCConsts.PENDING_REQUEST_ADD_STATE.equals(state)) {
                boolean groupMemberExists = groupMemberExists(groupId, principalId, principal, groupMember.getPendingState(), caller);
                result = insertStandardGroupMember(groupId, principalId, groupMember, admin,
                        principal, auditRef, groupMemberExists, true, caller);
            } else if (JDBCConsts.PENDING_REQUEST_DELETE_STATE.equals(state)) {
                result = deleteGroupMember(domainName, groupName, principal, admin, auditRef);
            }
            if (result) {
                executeDeletePendingGroupMember(groupId, principalId, admin, principal,
                        auditRef, false, caller);
            }
        } else {
            result = executeDeletePendingGroupMember(groupId, principalId, admin,
                    principal, auditRef, true, caller);
        }

        return result;
    }

    private DomainGroupMember getGroupsForPrincipal(String caller, DomainGroupMember domainGroupMember, PreparedStatement ps) throws SQLException {

        try (ResultSet rs = executeQuery(ps, caller)) {
            while (rs.next()) {
                final String groupName = rs.getString(1);
                final String domain = rs.getString(2);

                GroupMember groupMember = new GroupMember();
                groupMember.setGroupName(groupName);
                groupMember.setDomainName(domain);

                java.sql.Timestamp expiration = rs.getTimestamp(3);
                if (expiration != null) {
                    groupMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                }
                groupMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(4), 0));

                domainGroupMember.getMemberGroups().add(groupMember);
            }

            return domainGroupMember;
        }
    }

    @Override
    public DomainGroupMembers listDomainGroupMembers(String domainName) throws ServerResourceException {

        final String caller = "listDomainGroupMembers";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }

        DomainGroupMembers domainGroupMembers = new DomainGroupMembers();
        domainGroupMembers.setDomainName(domainName);

        Map memberMap = new HashMap<>();

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_GROUP_MEMBERS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    final String groupName = rs.getString(1);
                    final String memberName = rs.getString(2);

                    DomainGroupMember domainGroupMember = memberMap.get(memberName);
                    if (domainGroupMember == null) {
                        domainGroupMember = new DomainGroupMember();
                        domainGroupMember.setMemberName(memberName);
                        memberMap.put(memberName, domainGroupMember);
                    }

                    List members = domainGroupMember.getMemberGroups();
                    if (members == null) {
                        members = new ArrayList<>();
                        domainGroupMember.setMemberGroups(members);
                    }
                    GroupMember groupMember = new GroupMember();
                    groupMember.setGroupName(groupName);
                    java.sql.Timestamp expiration = rs.getTimestamp(3);
                    if (expiration != null) {
                        groupMember.setExpiration(Timestamp.fromMillis(expiration.getTime()));
                    }
                    groupMember.setSystemDisabled(nullIfDefaultValue(rs.getInt(4), 0));
                    members.add(groupMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        if (!memberMap.isEmpty()) {
            domainGroupMembers.setMembers(new ArrayList<>(memberMap.values()));
        }
        return domainGroupMembers;
    }

    @Override
    public DomainGroupMember getPrincipalGroups(String principal, String domainName) throws ServerResourceException {

        final String caller = "getPrincipalGroups";

        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        DomainGroupMember domainGroupMember = new DomainGroupMember();
        domainGroupMember.setMemberGroups(new ArrayList<>());
        domainGroupMember.setMemberName(principal);
        if (StringUtil.isEmpty(domainName)) {
            try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL_GROUPS)) {
                ps.setInt(1, principalId);
                return getGroupsForPrincipal(caller, domainGroupMember, ps);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        } else {
            int domainId = getDomainId(domainName);
            if (domainId == 0) {
                throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
            }
            try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL_GROUPS_DOMAIN)) {
                ps.setInt(1, principalId);
                ps.setInt(2, domainId);
                return getGroupsForPrincipal(caller, domainGroupMember, ps);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
    }

    @Override
    public List listGroupsWithUserAuthorityRestrictions() throws ServerResourceException {

        final String caller = "listGroupsWithUserAuthorityRestrictions";
        List groups = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_GROUPS_WITH_RESTRICTIONS)) {

            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    PrincipalGroup group = new PrincipalGroup();
                    group.setDomainName(rs.getString(JDBCConsts.DB_COLUMN_AS_DOMAIN_NAME));
                    group.setGroupName(rs.getString(JDBCConsts.DB_COLUMN_AS_GROUP_NAME));
                    group.setDomainUserAuthorityFilter(rs.getString(JDBCConsts.DB_COLUMN_AS_DOMAIN_USER_AUTHORITY_FILTER));
                    groups.add(group);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }

        return groups;
    }

    @Override
    public GroupMember getPendingGroupMember(String domainName, String groupName, String memberName) throws ServerResourceException {

        final String caller = "getPendingGroupMember";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PENDING_GROUP_MEMBER)) {
            ps.setInt(1, groupId);
            ps.setString(2, memberName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    GroupMember groupMember = new GroupMember();
                    groupMember.setMemberName(memberName);
                    groupMember.setRequestPrincipal(rs.getString(JDBCConsts.DB_COLUMN_REQ_PRINCIPAL));
                    groupMember.setPendingState(rs.getString(JDBCConsts.DB_COLUMN_PENDING_STATE));
                    return groupMember;
                } else {
                    return null;
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
    }

    @Override
    public boolean updatePrincipal(String principal, int newState) throws ServerResourceException {
        final String caller = "updatePrincipal";

        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_PRINCIPAL)) {
            ps.setInt(1, newState);
            ps.setString(2, principal);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    PrincipalMember savePrincipal(ResultSet rs) throws SQLException {
        return new PrincipalMember()
                .setPrincipalName(rs.getString(JDBCConsts.DB_COLUMN_NAME))
                .setSuspendedState(rs.getInt(JDBCConsts.DB_COLUMN_SYSTEM_SUSPENDED));
    }

    @Override
    public List getPrincipals(int queriedState) throws ServerResourceException {
        final String caller = "getPrincipals";
        List principals = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPALS)) {
            ps.setInt(1, queriedState);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    principals.add(savePrincipal(rs));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return principals;
    }

    @Override
    public PrincipalMember getPrincipal(String principalName) throws ServerResourceException {
        final String caller = "getPrincipal";
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_PRINCIPAL)) {
            ps.setString(1, principalName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    return savePrincipal(rs);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return null;
    }

    // To avoid firing multiple queries against DB, this function will generate 1 consolidated query for all domains->templates combination
    public String generateDomainTemplateVersionQuery(Map templateNameAndLatestVersion) {
        StringBuilder query = new StringBuilder();
        query.append("SELECT domain.name, domain_template.template FROM domain_template " +
                "JOIN domain ON domain_template.domain_id=domain.domain_id WHERE ");
        for (String templateName : templateNameAndLatestVersion.keySet()) {
            query.append("(domain_template.template = '").append(templateName).append("' and current_version < ")
                    .append(templateNameAndLatestVersion.get(templateName)).append(") OR ");
        }
        //To remove the last occurrence of "OR" from the generated query
        query.delete(query.lastIndexOf(") OR"), query.lastIndexOf("OR") + 3).append(");");
        return query.toString();
    }

    ServerResourceException notFoundError(String caller, String objectType, String objectName) {
        rollbackChanges();
        String message = "unknown " + objectType + " - " + objectName;
        return Utils.notFoundError(message, caller);
    }

    ServerResourceException requestError(String caller, String message) {
        rollbackChanges();
        return Utils.requestError(message, caller);
    }

    ServerResourceException internalServerError(String caller, String message) {
        rollbackChanges();
        return Utils.internalServerError(message, caller);
    }

    ServerResourceException sqlError(SQLException ex, String caller) {

        // check to see if this is a conflict error in which case
        // we're going to let the server to retry the caller
        // The SQL states that are 'retry-able' are 08S01
        // for a communications error, and 40001 for deadlock, 3101 for transaction rollback.
        // also check for the error code where the mysql server is
        // in read-mode which could happen if we had a failover
        // and the connections are still going to the old master

        final String sqlState = ex.getSQLState();
        int code = ServerResourceException.INTERNAL_SERVER_ERROR;
        String msg;
        if (MYSQL_EXC_STATE_COMM_ERROR.equals(sqlState) || MYSQL_EXC_STATE_DEADLOCK.equals(sqlState)) {
            code = ServerResourceException.CONFLICT;
            msg = "Concurrent update conflict, please retry your operation later.";
        } else if (ex.getErrorCode() == MYSQL_ER_TRANSACTION_ROLLBACK_DURING_COMMIT) {
            code = ServerResourceException.CONFLICT;
            msg = "Plugin instructed the server to rollback the current transaction.";
        } else if (ex.getErrorCode() == MYSQL_ER_OPTION_PREVENTS_STATEMENT) {
            code = ServerResourceException.GONE;
            msg = "MySQL Database running in read-only mode";
        } else if (ex.getErrorCode() == MYSQL_ER_OPTION_DUPLICATE_ENTRY) {
            code = ServerResourceException.BAD_REQUEST;
            msg = "Entry already exists";
        } else if (ex instanceof SQLTimeoutException) {
            code = ServerResourceException.SERVICE_UNAVAILABLE;
            msg = "Statement cancelled due to timeout";
        } else {
            msg = ex.getMessage() + ", state: " + sqlState + ", code: " + ex.getErrorCode();
        }
        rollbackChanges();
        return Utils.error(code, msg, caller);
    }

    Boolean nullIfDefaultValue(boolean flag, boolean defaultValue) {
        return flag == defaultValue ? null : flag;
    }

    Integer nullIfDefaultValue(int value, int defaultValue) {
        return value == defaultValue ? null : value;
    }

    private void addTagsToRoles(Map roleMap, String domainName) throws ServerResourceException {

        Map> domainRoleTags = getDomainRoleTags(domainName);
        if (domainRoleTags != null) {
            for (Map.Entry roleEntry : roleMap.entrySet()) {
                Map roleTag = domainRoleTags.get(roleEntry.getKey());
                if (roleTag != null) {
                    roleEntry.getValue().setTags(roleTag);
                }
            }
        }
    }

    private void addTagsToServices(Map serviceMap, String domainName) throws ServerResourceException {

        Map> domainServiceTags = getServiceResourceTags(domainName);
        if (domainServiceTags != null) {
            for (Map.Entry serviceEntry : serviceMap.entrySet()) {
                Map serviceTag = domainServiceTags.get(serviceEntry.getKey());
                if (serviceTag != null) {
                    serviceEntry.getValue().setTags(serviceTag);
                }
            }
        }
    }

    Map> getDomainRoleTags(String domainName) throws ServerResourceException {
        final String caller = "getDomainRoleTags";
        Map> domainRoleTags = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_ROLE_TAGS)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String roleName = rs.getString(1);
                    String tagKey = rs.getString(2);
                    String tagValue = rs.getString(3);
                    if (domainRoleTags == null) {
                        domainRoleTags = new HashMap<>();
                    }
                    Map roleTag = domainRoleTags.computeIfAbsent(roleName, tags -> new HashMap<>());
                    TagValueList tagValues = roleTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return domainRoleTags;
    }

    @Override
    public Map getRoleTags(String domainName, String roleName) throws ServerResourceException {
        final String caller = "getRoleTags";
        Map roleTag = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE_TAGS)) {
            ps.setString(1, domainName);
            ps.setString(2, roleName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String tagKey = rs.getString(1);
                    String tagValue = rs.getString(2);
                    if (roleTag == null) {
                        roleTag = new HashMap<>();
                    }
                    TagValueList tagValues = roleTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return roleTag;
    }

    @Override
    public boolean insertRoleTags(String roleName, String domainName, Map roleTags) throws ServerResourceException {
        final String caller = "insertRoleTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        int curTagCount = getRoleTagsCount(roleId);
        int newTagCount = calculateTagCount(roleTags);
        if (curTagCount + newTagCount > roleTagsLimit) {
            throw requestError(caller, "role tag quota exceeded - limit: "
                + roleTagsLimit + ", current tags count: " + curTagCount + ", new tags count: " + newTagCount);
        }

        boolean res = true;
        for (Map.Entry e : roleTags.entrySet()) {
            for (String tagValue : e.getValue().getList()) {
                try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ROLE_TAG)) {
                    ps.setInt(1, roleId);
                    ps.setString(2, processInsertValue(e.getKey()));
                    ps.setString(3, processInsertValue(tagValue));
                    res &= (executeUpdate(ps, caller) > 0);
                } catch (SQLException ex) {
                    throw sqlError(ex, caller);
                }
            }
        }
        return res;
    }

    int getRoleTagsCount(int roleId) throws ServerResourceException {
        final String caller = "getRoleTagsCount";
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_ROLE_TAG_COUNT)) {
            ps.setInt(1, roleId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public boolean deleteRoleTags(String roleName, String domainName, Set tagKeys) throws ServerResourceException {
        final String caller = "deleteRoleTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int roleId = getRoleId(domainId, roleName);
        if (roleId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_ROLE, ResourceUtils.roleResourceName(domainName, roleName));
        }
        boolean res = true;
        for (String tagKey : tagKeys) {
            try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ROLE_TAG)) {
                ps.setInt(1, roleId);
                ps.setString(2, processInsertValue(tagKey));
                res &= (executeUpdate(ps, caller) > 0);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
        return res;
    }

    @Override
    public boolean insertServiceTags(String serviceName, String domainName, Map serviceTags) throws ServerResourceException {
        final String caller = "insertServiceTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        int curTagCount = getServiceTagsCount(serviceId);
        int newTagCount = calculateTagCount(serviceTags);
        if (curTagCount + newTagCount > serviceTagsLimit) {
            throw requestError(caller, "service tag quota exceeded - limit: "
                    + serviceTagsLimit + ", current tags count: " + curTagCount + ", new tags count: " + newTagCount);
        }

        boolean res = true;
        for (Map.Entry e : serviceTags.entrySet()) {
            for (String tagValue : e.getValue().getList()) {
                try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_SERVICE_TAG)) {
                    ps.setInt(1, serviceId);
                    ps.setString(2, processInsertValue(e.getKey()));
                    ps.setString(3, processInsertValue(tagValue));
                    res &= (executeUpdate(ps, caller) > 0);
                } catch (SQLException ex) {
                    throw sqlError(ex, caller);
                }
            }
        }
        return res;
    }

    int getServiceTagsCount(int serviceId) throws ServerResourceException {
        final String caller = "getServiceTagsCount";
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_SERVICE_TAG_COUNT)) {
            ps.setInt(1, serviceId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public boolean deleteServiceTags(String serviceName, String domainName, Set tagKeys) throws ServerResourceException {
        final String caller = "deleteServiceTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int serviceId = getServiceId(domainId, serviceName);
        if (serviceId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_SERVICE, ResourceUtils.serviceResourceName(domainName, serviceName));
        }
        boolean res = true;
        for (String tagKey : tagKeys) {
            try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_SERVICE_TAG)) {
                ps.setInt(1, serviceId);
                ps.setString(2, processInsertValue(tagKey));
                res &= (executeUpdate(ps, caller) > 0);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
        return res;
    }

    @Override
    public Map getServiceTags(String domainName, String serviceName) throws ServerResourceException {
        final String caller = "getServiceTags";
        Map serviceTag = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_SERVICE_TAGS)) {
            ps.setString(1, domainName);
            ps.setString(2, serviceName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String tagKey = rs.getString(1);
                    String tagValue = rs.getString(2);
                    if (serviceTag == null) {
                        serviceTag = new HashMap<>();
                    }
                    TagValueList tagValues = serviceTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return serviceTag;
    }



    private void addTagsToGroups(Map groupMap, String domainName) throws ServerResourceException {

        Map> domainGroupTags = getDomainGroupTags(domainName);
        if (domainGroupTags != null) {
            for (Map.Entry groupEntry : groupMap.entrySet()) {
                Map groupTag = domainGroupTags.get(groupEntry.getKey());
                if (groupTag != null) {
                    groupEntry.getValue().setTags(groupTag);
                }
            }
        }
    }

    Map> getServiceResourceTags(String domainName) throws ServerResourceException {
        final String funcCaller = "getDomainServiceTags";
        Map> domainResourceTags = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_SERVICE_TAGS)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, funcCaller)) {
                while (rs.next()) {
                    String resourceName = rs.getString(1);
                    String tagKey = rs.getString(2);
                    String tagValue = rs.getString(3);
                    if (domainResourceTags == null) {
                        domainResourceTags = new HashMap<>();
                    }
                    Map resourceTag = domainResourceTags.computeIfAbsent(resourceName, tags -> new HashMap<>());
                    TagValueList tagValues = resourceTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, funcCaller);
        }
        return domainResourceTags;
    }

    Map> getDomainGroupTags(String domainName) throws ServerResourceException {
        final String caller = "getDomainGroupTags";
        Map> domainGroupTags = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_DOMAIN_GROUP_TAGS)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String groupName = rs.getString(1);
                    String tagKey = rs.getString(2);
                    String tagValue = rs.getString(3);
                    if (domainGroupTags == null) {
                        domainGroupTags = new HashMap<>();
                    }
                    Map groupTag = domainGroupTags.computeIfAbsent(groupName, tags -> new HashMap<>());
                    TagValueList tagValues = groupTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return domainGroupTags;
    }

    @Override
    public Map getGroupTags(String domainName, String groupName) throws ServerResourceException {
        final String caller = "getGroupTags";
        Map groupTag = null;

        try (PreparedStatement ps = con.prepareStatement(SQL_GET_GROUP_TAGS)) {
            ps.setString(1, domainName);
            ps.setString(2, groupName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String tagKey = rs.getString(1);
                    String tagValue = rs.getString(2);
                    if (groupTag == null) {
                        groupTag = new HashMap<>();
                    }
                    TagValueList tagValues = groupTag.computeIfAbsent(tagKey, k -> new TagValueList().setList(new ArrayList<>()));
                    tagValues.getList().add(tagValue);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return groupTag;
    }

    @Override
    public boolean insertGroupTags(String groupName, String domainName, Map groupTags) throws ServerResourceException {

        final String caller = "insertGroupTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        int curTagCount = getGroupTagsCount(groupId);
        int newTagCount = calculateTagCount(groupTags);
        if (curTagCount + newTagCount > groupTagsLimit) {
            throw requestError(caller, "group tag quota exceeded - limit: "
                    + groupTagsLimit + ", current tags count: " + curTagCount + ", new tags count: " + newTagCount);
        }

        boolean res = true;
        for (Map.Entry e : groupTags.entrySet()) {
            for (String tagValue : e.getValue().getList()) {
                try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_GROUP_TAG)) {
                    ps.setInt(1, groupId);
                    ps.setString(2, processInsertValue(e.getKey()));
                    ps.setString(3, processInsertValue(tagValue));
                    res &= (executeUpdate(ps, caller) > 0);
                } catch (SQLException ex) {
                    throw sqlError(ex, caller);
                }
            }
        }
        return res;

    }

    int getGroupTagsCount(int groupId) throws ServerResourceException {
        final String caller = "getGroupTagsCount";
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_GROUP_TAG_COUNT)) {
            ps.setInt(1, groupId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public boolean deleteGroupTags(String groupName, String domainName, Set tagKeys) throws ServerResourceException {
        final String caller = "deleteGroupTags";

        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int groupId = getGroupId(domainId, groupName);
        if (groupId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_GROUP, ResourceUtils.groupResourceName(domainName, groupName));
        }
        boolean res = true;
        for (String tagKey : tagKeys) {
            try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_GROUP_TAG)) {
                ps.setInt(1, groupId);
                ps.setString(2, processInsertValue(tagKey));
                res &= (executeUpdate(ps, caller) > 0);
            } catch (SQLException ex) {
                throw sqlError(ex, caller);
            }
        }
        return res;
    }

    @Override
    public int countAssertionConditions(long assertionId) throws ServerResourceException {

        final String caller = "countAssertionConditions";
        int count = 0;
        try (PreparedStatement ps = con.prepareStatement(SQL_COUNT_ASSERTION_CONDITIONS)) {
            ps.setLong(1, assertionId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    count = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return count;
    }

    @Override
    public List getAssertionConditions(long assertionId) throws ServerResourceException {
        final String caller = "getAssertionConditions";
        List assertionConditions = new ArrayList<>();
        Map assertionConditionMap = new HashMap<>();
        int conditionId;
        AssertionCondition assertionCondition;
        Map assertionConditionDataMap;
        AssertionConditionData assertionConditionData;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ASSERTION_CONDITIONS)) {
            ps.setLong(1, assertionId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    conditionId = rs.getInt(JDBCConsts.DB_COLUMN_CONDITION_ID);
                    assertionCondition = assertionConditionMap.get(conditionId);
                    if (assertionCondition == null) {
                        assertionCondition = new AssertionCondition();
                        assertionConditionDataMap = new HashMap<>();
                        assertionCondition.setConditionsMap(assertionConditionDataMap);
                        assertionCondition.setId(conditionId);
                        assertionConditionMap.put(conditionId, assertionCondition);
                        assertionConditions.add(assertionCondition);
                    }
                    assertionConditionData = new AssertionConditionData();
                    if (rs.getString(JDBCConsts.DB_COLUMN_OPERATOR) != null) {
                        assertionConditionData.setOperator(AssertionConditionOperator.fromString(rs.getString(JDBCConsts.DB_COLUMN_OPERATOR)));
                    }
                    assertionConditionData.setValue(rs.getString(JDBCConsts.DB_COLUMN_VALUE));
                    assertionCondition.getConditionsMap().put(rs.getString(JDBCConsts.DB_COLUMN_KEY), assertionConditionData);

                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return assertionConditions;
    }

    @Override
    public AssertionCondition getAssertionCondition(long assertionId, int conditionId) throws ServerResourceException {
        final String caller = "getAssertionCondition";
        AssertionCondition assertionCondition = null;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ASSERTION_CONDITION)) {
            ps.setLong(1, assertionId);
            ps.setInt(2, conditionId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    if (assertionCondition == null) {
                        assertionCondition = new AssertionCondition();
                        assertionCondition.setId(rs.getInt(JDBCConsts.DB_COLUMN_CONDITION_ID));
                        Map conditionDataMap = new HashMap<>();
                        assertionCondition.setConditionsMap(conditionDataMap);
                    }
                    AssertionConditionData conditionData = new AssertionConditionData();
                    if (rs.getString(JDBCConsts.DB_COLUMN_OPERATOR) != null) {
                        conditionData.setOperator(AssertionConditionOperator.fromString(rs.getString(JDBCConsts.DB_COLUMN_OPERATOR)));
                    }
                    conditionData.setValue(rs.getString(JDBCConsts.DB_COLUMN_VALUE));
                    assertionCondition.getConditionsMap().put(rs.getString(JDBCConsts.DB_COLUMN_KEY), conditionData);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return assertionCondition;
    }

    @Override
    public boolean insertAssertionConditions(long assertionId, AssertionConditions assertionConditions) throws ServerResourceException {
        final String caller = "insertAssertionConditions";
        boolean result = true;
        for (AssertionCondition assertionCondition : assertionConditions.getConditionsList()) {
            // get condition id for each AssertionCondition object in the list
            // all keys in the conditionMap of AssertionCondition object share same condition id
            assertionCondition.setId(getNextConditionId(assertionId, caller));
            result = result && insertSingleAssertionCondition(assertionId, assertionCondition, caller);
        }
        return result;
    }

    @Override
    public boolean deleteAssertionConditions(long assertionId) throws ServerResourceException {
        final String caller = "deleteAssertionConditions";
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ASSERTION_CONDITIONS)) {
            ps.setLong(1, assertionId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return affectedRows > 0;
    }

    @Override
    public boolean insertAssertionCondition(long assertionId, AssertionCondition assertionCondition) throws ServerResourceException {
        final String caller = "insertAssertionCondition";
        return insertSingleAssertionCondition(assertionId, assertionCondition, caller);
    }

    @Override
    public int getNextConditionId(long assertionId, String caller) throws ServerResourceException {
        int nextConditionId = 1;
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_NEXT_CONDITION_ID)) {
            ps.setLong(1, assertionId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    nextConditionId = rs.getInt(1);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return nextConditionId;
    }

    private boolean insertSingleAssertionCondition(long assertionId, AssertionCondition assertionCondition, String caller) throws ServerResourceException {
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_ASSERTION_CONDITION)) {
            // loop over all the keys in the given condition map
            for (String key : assertionCondition.getConditionsMap().keySet()) {
                ps.setLong(1, assertionId);
                ps.setInt(2, assertionCondition.getId());
                ps.setString(3, key);
                ps.setString(4, assertionCondition.getConditionsMap().get(key).getOperator().name());
                ps.setString(5, assertionCondition.getConditionsMap().get(key).getValue());

                ps.addBatch();
            }
            affectedRows = Arrays.stream(executeBatch(ps, caller)).sum();

        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return affectedRows > 0;
    }

    @Override
    public boolean deleteAssertionCondition(long assertionId, int conditionId) throws ServerResourceException {
        final String caller = "deleteAssertionCondition";
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_ASSERTION_CONDITION)) {
            ps.setLong(1, assertionId);
            ps.setInt(2, conditionId);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return affectedRows > 0;
    }

    @Override
    public List listServiceDependencies(String domainName) throws ServerResourceException {

        final String caller = "listServiceDependencies";

        List services = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_SERVICE_DEPENDENCIES)) {
            ps.setString(1, domainName);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    services.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(services);
        return services;
    }

    @Override
    public List listDomainDependencies(String service) throws ServerResourceException {

        final String caller = "listServiceDependencies";

        List domains = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_DEPENDENCIES)) {
            ps.setString(1, service);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    domains.add(rs.getString(1));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        Collections.sort(domains);
        return domains;
    }


    @Override
    public List getAllExpiredRoleMembers(int limit, int offset, int serverPurgeExpiryDays) throws ServerResourceException {
        final String caller = "getAllExpiredRoleMembers";
        List members = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(GET_ALL_EXPIRED_ROLE_MEMBERS)) {
            ps.setInt(1, serverPurgeExpiryDays);
            ps.setInt(2, limit);
            ps.setInt(3, offset);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    ExpiryMember expiryMember = new ExpiryMember()
                            .setDomainName(rs.getString(JDBCConsts.DB_COLUMN_AS_DOMAIN_NAME))
                            .setCollectionName(rs.getString(JDBCConsts.DB_COLUMN_AS_ROLE_NAME))
                            .setPrincipalName(rs.getString(JDBCConsts.DB_COLUMN_AS_PRINCIPAL_NAME))
                            .setExpiration(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION).getTime()));
                    members.add(expiryMember);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return members;
    }

    @Override
    public List getAllExpiredGroupMembers(int limit, int offset, int serverPurgeExpiryDays) throws ServerResourceException {
        final String caller = "getAllExpiredGroupMembers";
        List members = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(GET_ALL_EXPIRED_GROUP_MEMBERS)) {
            ps.setInt(1, serverPurgeExpiryDays);
            ps.setInt(2, limit);
            ps.setInt(3, offset);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    String domainName = rs.getString(JDBCConsts.DB_COLUMN_AS_DOMAIN_NAME);
                    ExpiryMember member = new ExpiryMember()
                            .setDomainName(domainName)
                            .setCollectionName(rs.getString(JDBCConsts.DB_COLUMN_AS_GROUP_NAME))
                            .setPrincipalName(rs.getString(JDBCConsts.DB_COLUMN_AS_PRINCIPAL_NAME))
                            .setExpiration(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_EXPIRATION).getTime()));
                    members.add(member);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return members;
    }

    @Override
    public boolean insertDomainDependency(String dependencyDomainName, String service) throws ServerResourceException {
        int affectedRows;
        final String caller = "insertDomainDependency";

        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN_DEPENDENCY)) {
            ps.setString(1, dependencyDomainName);
            ps.setString(2, service);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteDomainDependency(String dependencyDomainName, String service) throws ServerResourceException {
        int affectedRows;
        final String caller = "deleteDomainDependency";

        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN_DEPENDENCY)) {
            ps.setString(1, dependencyDomainName);
            ps.setString(2, service);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    Policy savePolicySettings(final String domainName, final String policyName,  ResultSet rs) throws SQLException {
        return new Policy()
                .setName(ResourceUtils.policyResourceName(domainName, policyName))
                .setModified(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_MODIFIED).getTime()))
                .setActive(rs.getBoolean(JDBCConsts.DB_COLUMN_ACTIVE))
                .setVersion(rs.getString(JDBCConsts.DB_COLUMN_VERSION))
                .setResourceOwnership(ResourceOwnership.getResourcePolicyOwnership(rs.getString(JDBCConsts.DB_COLUMN_RESOURCE_OWNER)));
    }

    boolean isLastNotifyTimeWithinSpecifiedDays(final String sqlCmd, int delayDays) {

        final String caller = "isLastNotifyTimeWithinSpecifiedDays";

        long lastRunTime = 0;
        try (PreparedStatement ps = con.prepareStatement(sqlCmd)) {
            try (ResultSet rs = executeQuery(ps, caller)) {
                if (rs.next()) {
                    lastRunTime = rs.getTimestamp(1).getTime();
                }
            }
        } catch (SQLException ex) {
            LOG.error("unable to retrieve last notification run time: {}", ex.getMessage());
            return false;
        }
        return System.currentTimeMillis() - lastRunTime < TimeUnit.DAYS.toMillis(delayDays);
    }

    @Override
    public ReviewObjects getRolesForReview(String principal) throws ServerResourceException {

        final String caller = "getRolesForReview";

        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        List reviewRoles = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_ROLE_REVIEW_LIST)) {
            ps.setInt(1, principalId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    ReviewObject reviewObject = new ReviewObject()
                            .setDomainName(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_NAME))
                            .setName(rs.getString(JDBCConsts.DB_COLUMN_AS_ROLE_NAME))
                            .setMemberExpiryDays(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_EXPIRY_DAYS))
                            .setServiceExpiryDays(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_EXPIRY_DAYS))
                            .setGroupExpiryDays(rs.getInt(JDBCConsts.DB_COLUMN_GROUP_EXPIRY_DAYS))
                            .setMemberReviewDays(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_REVIEW_DAYS))
                            .setServiceReviewDays(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_REVIEW_DAYS))
                            .setGroupReviewDays(rs.getInt(JDBCConsts.DB_COLUMN_GROUP_REVIEW_DAYS))
                            .setCreated(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_CREATED).getTime()));
                    java.sql.Timestamp lastReviewedTime = rs.getTimestamp(JDBCConsts.DB_COLUMN_LAST_REVIEWED_TIME);
                    if (lastReviewedTime != null) {
                        reviewObject.setLastReviewedDate(Timestamp.fromMillis(lastReviewedTime.getTime()));
                    }
                    reviewRoles.add(reviewObject);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return new ReviewObjects().setList(reviewRoles);
    }

    @Override
    public ReviewObjects getGroupsForReview(String principal) throws ServerResourceException {

        final String caller = "getGroupsForReview";

        int principalId = getPrincipalId(principal);
        if (principalId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_PRINCIPAL, principal);
        }

        List reviewRoles = new ArrayList<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_GET_GROUP_REVIEW_LIST)) {
            ps.setInt(1, principalId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    ReviewObject reviewObject = new ReviewObject()
                            .setDomainName(rs.getString(JDBCConsts.DB_COLUMN_DOMAIN_NAME))
                            .setName(rs.getString(JDBCConsts.DB_COLUMN_AS_GROUP_NAME))
                            .setMemberExpiryDays(rs.getInt(JDBCConsts.DB_COLUMN_MEMBER_EXPIRY_DAYS))
                            .setServiceExpiryDays(rs.getInt(JDBCConsts.DB_COLUMN_SERVICE_EXPIRY_DAYS))
                            .setCreated(Timestamp.fromMillis(rs.getTimestamp(JDBCConsts.DB_COLUMN_CREATED).getTime()));
                    java.sql.Timestamp lastReviewedTime = rs.getTimestamp(JDBCConsts.DB_COLUMN_LAST_REVIEWED_TIME);
                    if (lastReviewedTime != null) {
                        reviewObject.setLastReviewedDate(Timestamp.fromMillis(lastReviewedTime.getTime()));
                    }
                    reviewRoles.add(reviewObject);
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return new ReviewObjects().setList(reviewRoles);
    }

    @Override
    public boolean insertDomainContact(String domainName, String contactType, String username) throws ServerResourceException {

        final String caller = "insertDomainContact";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_INSERT_DOMAIN_CONTACT)) {
            ps.setInt(1, domainId);
            ps.setString(2, contactType);
            ps.setString(3, username);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean updateDomainContact(String domainName, String contactType, String username) throws ServerResourceException {

        final String caller = "updateDomainContact";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_UPDATE_DOMAIN_CONTACT)) {
            ps.setString(1, username);
            ps.setInt(2, domainId);
            ps.setString(3, contactType);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean deleteDomainContact(String domainName, String contactType) throws ServerResourceException {

        final String caller = "deleteDomainContact";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_DELETE_DOMAIN_CONTACT)) {
            ps.setInt(1, domainId);
            ps.setString(2, contactType);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public Map> listContactDomains(String username) throws ServerResourceException {

        final String caller = "listContactDomains";

        Map> contactDomains = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_CONTACT_DOMAINS)) {
            ps.setString(1, username);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    List contactTypes = contactDomains.computeIfAbsent(rs.getString(1), k -> new ArrayList<>());
                    contactTypes.add(rs.getString(2));
                }
            }
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return contactDomains;
    }

    public Map getDomainContacts(int domainId) throws SQLException {

        final String caller = "getDomainContacts";
        Map domainContacts = new HashMap<>();
        try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAIN_CONTACTS)) {
            ps.setInt(1, domainId);
            try (ResultSet rs = executeQuery(ps, caller)) {
                while (rs.next()) {
                    domainContacts.put(rs.getString(JDBCConsts.DB_COLUMN_TYPE), rs.getString(JDBCConsts.DB_COLUMN_NAME));
                }
            }
        }
        return domainContacts;
    }

    @Override
    public boolean setResourceDomainOwnership(String domainName, ResourceDomainOwnership resourceOwner) throws ServerResourceException {
        final String caller = "setResourceDomainOwnership";
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_SET_DOMAIN_RESOURCE_OWNERSHIP)) {
            ps.setString(1, ResourceOwnership.generateResourceOwnerString(resourceOwner));
            ps.setString(2, domainName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean setResourceRoleOwnership(String domainName, String roleName, ResourceRoleOwnership resourceOwner) throws ServerResourceException {
        final String caller = "setResourceRoleOwnership";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_SET_ROLE_RESOURCE_OWNERSHIP)) {
            ps.setString(1, ResourceOwnership.generateResourceOwnerString(resourceOwner));
            ps.setInt(2, domainId);
            ps.setString(3, roleName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean setResourceGroupOwnership(String domainName, String groupName, ResourceGroupOwnership resourceOwner) throws ServerResourceException {
        final String caller = "setResourceGroupOwnership";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_SET_GROUP_RESOURCE_OWNERSHIP)) {
            ps.setString(1, ResourceOwnership.generateResourceOwnerString(resourceOwner));
            ps.setInt(2, domainId);
            ps.setString(3, groupName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean setResourcePolicyOwnership(String domainName, String policyName, ResourcePolicyOwnership resourceOwner) throws ServerResourceException {
        final String caller = "setResourcePolicyOwnership";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_SET_POLICY_RESOURCE_OWNERSHIP)) {
            ps.setString(1, ResourceOwnership.generateResourceOwnerString(resourceOwner));
            ps.setInt(2, domainId);
            ps.setString(3, policyName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }

    @Override
    public boolean setResourceServiceOwnership(String domainName, String serviceName, ResourceServiceIdentityOwnership resourceOwner) throws ServerResourceException {
        final String caller = "setResourceServiceOwnership";
        int domainId = getDomainId(domainName);
        if (domainId == 0) {
            throw notFoundError(caller, JDBCConsts.OBJECT_DOMAIN, domainName);
        }
        int affectedRows;
        try (PreparedStatement ps = con.prepareStatement(SQL_SET_SERVICE_RESOURCE_OWNERSHIP)) {
            ps.setString(1, ResourceOwnership.generateResourceOwnerString(resourceOwner));
            ps.setInt(2, domainId);
            ps.setString(3, serviceName);
            affectedRows = executeUpdate(ps, caller);
        } catch (SQLException ex) {
            throw sqlError(ex, caller);
        }
        return (affectedRows > 0);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy