class ZTSImpl implements KeyStore, ZTSHandler {

    @Override
    public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceRegisterInformation info) {

        final String caller = ctx.getApiName();
        final String principalDomain = logPrincipalAndGetDomain(ctx);

        if (readOnlyMode) {
            throw requestError("Server in Maintenance Read-Only mode. Please try your request later",
                    caller, ZTSConsts.ZTS_UNKNOWN_DOMAIN, principalDomain);
        }
        
        validateRequest(ctx.request(), principalDomain, caller);
        validate(info, TYPE_INSTANCE_REGISTER_INFO, principalDomain, caller);
        
        // for consistent handling of all requests, we're going to convert
        // all incoming object values into lower case (e.g. domain, role,
        // policy, service, etc name)
        
        AthenzObject.INSTANCE_REGISTER_INFO.convertToLowerCase(info);

        final String domain = info.getDomain();
        setRequestDomain(ctx, domain);
        final String service = info.getService();
        final String cn = domain + "." + service;
        ((RsrcCtxWrapper) ctx).logPrincipal(cn);

        // before running any checks make sure it's coming from
        // an authorized ip address

        final String provider = info.getProvider();
        final String ipAddress = ServletRequestUtil.getRemoteAddress(ctx.request());
        if (!instanceCertManager.verifyInstanceCertIPAddress(provider, ipAddress)) {
            throw forbiddenError("Unknown IP: " + ipAddress + " for Provider: " + provider,
                    caller, domain, principalDomain);
        }

        // run the authorization checks to make sure the provider has been
        // authorized to launch instances in Athenz and the service has
        // authorized this provider to launch its instances
        
        Principal providerService = createPrincipalForName(provider);
        StringBuilder errorMsg = new StringBuilder(256);

        if (!instanceCertManager.authorizeLaunch(providerService, domain, service, errorMsg)) {
            throw forbiddenError(errorMsg.toString(), caller, domain, principalDomain);
        }

        // validate request/csr details
        
        X509ServiceCertRequest certReq;
        try {
            certReq = new X509ServiceCertRequest(info.getCsr());
        } catch (CryptoException ex) {
            throw requestError("unable to parse PKCS10 CSR: " + ex.getMessage(),
                    caller, domain, principalDomain);
        }

        final String serviceDnsSuffix = dataStore.getDomainData(domain).getCertDnsDomain();
        final DataCache athenzSysDomainCache = dataStore.getDataCache(ATHENZ_SYS_DOMAIN);

        if (!certReq.validate(domain, service, provider, validCertSubjectOrgValues, athenzSysDomainCache,
                serviceDnsSuffix, info.getHostname(), info.getHostCnames(), hostnameResolver, errorMsg)) {
            throw requestError("CSR validation failed - " + errorMsg.toString(),
                    caller, domain, principalDomain);
        }

        final String certReqInstanceId = certReq.getInstanceId();

        // validate attestation data is included in the request
        
        InstanceProvider instanceProvider = instanceProviderManager.getProvider(provider, hostnameResolver);
        if (instanceProvider == null) {
            throw requestError("unable to get instance for provider: " + provider,
                    caller, domain, principalDomain);
        }

        // include instance details in the query access log to help
        // with debugging requests

        ctx.request().setAttribute(ACCESS_LOG_ADDL_QUERY,
                getInstanceRegisterQueryLog(provider, certReqInstanceId, info.getHostname()));

        InstanceConfirmation instance = generateInstanceConfirmObject(ctx, provider, domain,
                service, info.getAttestationData(), certReqInstanceId, info.getHostname(),
                certReq, instanceProvider.getProviderScheme());

        // make sure to close our provider when its no longer needed

        Object timerProviderMetric = metric.startTiming("providerregister_timing", provider, principalDomain);
        try {
            instance = instanceProvider.confirmInstance(instance);
        } catch (com.yahoo.athenz.instance.provider.ResourceException ex) {
            metric.increment("providerconfirm_failure", domain, provider);
            int code = (ex.getCode() == ResourceException.GATEWAY_TIMEOUT) ?
                    ResourceException.GATEWAY_TIMEOUT : ResourceException.FORBIDDEN;
            throw error(code, getExceptionMsg("unable to verify attestation data: ", ctx, ex, info.getHostname()),
                    caller, domain, principalDomain);
        } catch (Exception ex) {
            metric.increment("providerconfirm_failure", domain, provider);
            throw forbiddenError(getExceptionMsg("unable to verify attestation data: ", ctx, ex, info.getHostname()),
                    caller, domain, principalDomain);
        } finally {
            metric.stopTiming(timerProviderMetric, provider, principalDomain);
            instanceProvider.close();
        }
        metric.increment("providerconfirm_success", domain, provider);

        // determine what type of certificate the provider is authorizing
        // this instance to get - possible values are: server, client or
        // null (indicating both client and server). Additionally, we're
        // going to see if the provider wants to impose an expiry time
        // though the certificate signer might decide to ignore that
        // request and override it with its own value. Other optional
        // attributes we get back from the provider include whether or
        // not the certs can be refreshed or ssh certs can be requested
        
        String certUsage = null;
        String certSubjectOU = null;
        String instancePrivateIp = null;
        int certExpiryTime = 0;
        boolean certRefresh = true;
        boolean sshCertAllowed = false;
        
        Map<String, String> instanceAttrs = instance.getAttributes();
        if (instanceAttrs != null) {
            certUsage = instanceAttrs.remove(InstanceProvider.ZTS_CERT_USAGE);
            certSubjectOU = instanceAttrs.remove(InstanceProvider.ZTS_CERT_SUBJECT_OU);
            instancePrivateIp = instanceAttrs.remove(InstanceProvider.ZTS_INSTANCE_PRIVATE_IP);
            certExpiryTime = ZTSUtils.parseInt(instanceAttrs.remove(InstanceProvider.ZTS_CERT_EXPIRY_TIME), 0);
            certRefresh = ZTSUtils.parseBoolean(instanceAttrs.remove(InstanceProvider.ZTS_CERT_REFRESH), true);
            sshCertAllowed = ZTSUtils.parseBoolean(instanceAttrs.remove(InstanceProvider.ZTS_CERT_SSH), false);
        }

        // validate the CSR subject ou field. We're doing this check here
        // because the provider can tell us what the ou field should be

        if (verifyCertSubjectOU && !certReq.validateSubjectOUField(provider, certSubjectOU,
                validCertSubjectOrgUnitValues)) {
            throw requestError("CSR Subject OrgUnit validation failed",
                    caller, domain, principalDomain);
        }

        // update the expiry time if one is provided in the request

        certExpiryTime = getServiceCertRequestExpiryTime(certExpiryTime, info.getExpiryTime());

        // generate certificate for the instance

        Object timerX509CertMetric = metric.startTiming("certsignx509_timing", null, principalDomain);
        InstanceIdentity identity = instanceCertManager.generateIdentity(info.getCsr(), cn,
                certUsage, certExpiryTime);
        metric.stopTiming(timerX509CertMetric, null, principalDomain);

        if (identity == null) {
            throw serverError("unable to generate identity", caller, domain, principalDomain);
        }

        // if we're asked then we should also generate a ssh
        // certificate for the instance as well

        if (sshCertAllowed) {
            Object timerSSHCertMetric = metric.startTiming("certsignssh_timing", null, principalDomain);

            // generate a ssh object for recording

            SSHCertRecord certRecord = generateSSHCertRecord(ctx, cn, certReqInstanceId, instancePrivateIp);
            instanceCertManager.generateSSHIdentity(null, identity, info.getHostname(), info.getSsh(),
                    certRecord, ZTSConsts.ZTS_SSH_HOST);
            metric.stopTiming(timerSSHCertMetric, null, principalDomain);
        }

        // set the other required attributes in the identity object

        identity.setAttributes(instanceAttrs);
        identity.setProvider(provider);
        identity.setInstanceId(certReqInstanceId);

        X509Certificate newCert = Crypto.loadX509Certificate(identity.getX509Certificate());
        final String certSerial = newCert.getSerialNumber().toString();
        
        // need to update our cert record with new certificate details
        // unless we're told by the provider that refresh is not allowed
        // thus no need to register the instance details

        if (certRefresh) {

            // we must be able to update our database otherwise we will not be
            // able to validate the certificate during refresh operations

            if (insertX509CertRecord(ctx, cn, provider, certReqInstanceId, certSerial,
                    InstanceProvider.ZTS_CERT_USAGE_CLIENT.equalsIgnoreCase(certUsage), newCert.getNotAfter(), info.getHostname()) == null) {
                throw serverError("unable to update cert db", caller, domain, principalDomain);
            }
        }
        
        // if we're asked to return an NToken in addition to ZTS Certificate
        // then we'll generate one and include in the identity object
        
        if (info.getToken() == Boolean.TRUE) {
            PrincipalToken svcToken = new PrincipalToken.Builder("S1", domain, service)
                .expirationWindow(svcTokenTimeout).keyId(privateKey.getId()).host(serverHostName)
                .ip(ipAddress).keyService(ZTSConsts.ZTS_SERVICE).build();
            svcToken.sign(privateKey.getKey());
            identity.setServiceToken(svcToken.getSignedToken());
        }

        // log our certificate

        instanceCertManager.logX509Cert(null, ipAddress, provider, certReqInstanceId, newCert);

        final String location = "/zts/v1/instance/" + provider + "/" + domain
                + "/" + service + "/" + certReqInstanceId;
        return Response.status(ResourceException.CREATED).entity(identity)
                .header("Location", location).build();
    }

    public  ZTSImpl();
    public  ZTSImpl(CloudStore implCloudStore, DataStore implDataStore);

}