diff --git a/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala b/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala index 23415275a..9b47ef5f8 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala @@ -1,297 +1,297 @@ package net.shrine.happy import net.shrine.adapter.dao.AdapterDao import net.shrine.adapter.service.AdapterRequestHandler -import net.shrine.broadcaster.{Broadcaster, AdapterClientBroadcaster, NodeHandle} +import net.shrine.broadcaster.{Broadcaster, NodeHandle} import net.shrine.client.Poster import net.shrine.config.mappings.AdapterMappings -import net.shrine.crypto.{KeyStoreCertCollection, KeyStoreDescriptor, Signer, SigningCertStrategy} +import net.shrine.crypto.SigningCertStrategy import net.shrine.i2b2.protocol.pm.{GetUserConfigurationRequest, HiveConfig} import net.shrine.log.Loggable import net.shrine.ont.data.OntologyMetadata import net.shrine.protocol.query.{OccuranceLimited, QueryDefinition, Term} import net.shrine.protocol.{AuthenticationInfo, BroadcastMessage, Credential, Failure, NodeId, Result, ResultOutputType, RunQueryRequest, Timeout} import net.shrine.qep.dao.AuditDao import net.shrine.util.{StackTrace, Versions, XmlUtil} import net.shrine.wiring.ShrineOrchestrator import scala.concurrent.Await import scala.util.Try import scala.xml.{Node, NodeSeq} /** * @author Bill Simons * @since 6/20/11 * @see http://cbmi.med.harvard.edu * @see http://chip.org *

* NOTICE: This software comes with NO guarantees whatsoever and is * licensed as Lgpl Open Source * @see http://www.gnu.org/licenses/lgpl.html */ final class HappyShrineService( - keystoreDescriptor: KeyStoreDescriptor, - certCollection: KeyStoreCertCollection, - signer: Signer, pmPoster: Poster, ontologyMetadata: OntologyMetadata, adapterMappings: Option[AdapterMappings], auditDaoOption: Option[AuditDao], adapterDaoOption: Option[AdapterDao], adapterOption: Option[AdapterRequestHandler] ) extends HappyShrineRequestHandler with Loggable { info("Happy service initialized") private val notAnAdapter = "" private val notAHub = "" private val domain = "happy" private val username = "happy" private val networkAuthn = AuthenticationInfo(domain, username, Credential("", isToken = false)) override def keystoreReport: String = { + val keystoreDescriptor = ShrineOrchestrator.keyStoreDescriptor + val certCollection = ShrineOrchestrator.shrineCertCollection + val myCertId = certCollection.myCertId def unpack(name: Option[String]) = name.getOrElse("Unknown") XmlUtil.stripWhitespace { { keystoreDescriptor.file } { keystoreDescriptor.keyStoreType } { keystoreDescriptor.privateKeyAlias.getOrElse("unspecified") } { myCertId.map { myId => { unpack(myId.name) } { myId.serial } }.getOrElse { } } { certCollection.ids.map { certId => { unpack(certId.name) } { certId.serial } } } }.toString } private def nodeListAsXml: Iterable[Node] = { val noneResult: Iterable[Node] = Nil ShrineOrchestrator.broadcasterMultiplexerService.fold(noneResult) { broadcasterMultiplexerService => val broadcaster = broadcasterMultiplexerService.broadcaster broadcaster.destinations.map{ node:NodeHandle => { node.nodeId.name } { node.client.url.getOrElse("").toString } } } } override def routingReport: String = XmlUtil.stripWhitespace { { nodeListAsXml } }.toString override def hiveReport: String = { if(ShrineOrchestrator.shrineConfig.getBoolean("adapter.create")) { val credentials = ShrineOrchestrator.crcHiveCredentials val pmRequest = GetUserConfigurationRequest(credentials.toAuthenticationInfo) val response = pmPoster.post(pmRequest.toI2b2String) HiveConfig.fromI2b2(response.body).toXmlString } else notAnAdapter } private def failureToXml(failure: Failure): NodeSeq = { { failure.origin } { StackTrace.stackTraceAsString(failure.cause) } } private def timeoutToXml(timeout: Timeout): NodeSeq = { { timeout.origin } } override def networkReport: String = { ShrineOrchestrator.broadcasterMultiplexerService.fold(notAHub) { broadcasterMultiplexerService => val maxQueryWaitTime = broadcasterMultiplexerService.maxQueryWaitTime val broadcaster: Broadcaster = broadcasterMultiplexerService.broadcaster val message = newBroadcastMessageWithRunQueryRequest val multiplexer = broadcaster.broadcast(message) val responses = Await.result(multiplexer.responses, maxQueryWaitTime).toSeq val failures = responses.collect { case f: Failure => f } val timeouts = responses.collect { case t: Timeout => t } val validResults = responses.collect { case r: Result => r } val noProblems = failures.isEmpty && timeouts.isEmpty XmlUtil.stripWhitespace { {ShrineOrchestrator.localAdapterServiceOption.isDefined} {nodeListAsXml} {noProblems} {broadcaster.destinations.size} {validResults.size} {failures.size} {timeouts.size} {nodeListAsXml}{failures.map(failureToXml)}{timeouts.map(timeoutToXml)} }.toString } } val adapterStatusQuery = ShrineOrchestrator.shrineConfig.getString("networkStatusQuery") private def newRunQueryRequest(authn: AuthenticationInfo): RunQueryRequest = { val queryDefinition = QueryDefinition("TestQuery", OccuranceLimited(1, Term(adapterStatusQuery))) import scala.concurrent.duration._ RunQueryRequest( "happyProject", 3.minutes, authn, None, None, Set(ResultOutputType.PATIENT_COUNT_XML), queryDefinition) } private def newBroadcastMessageWithRunQueryRequest: BroadcastMessage = { val req = newRunQueryRequest(networkAuthn) - signer.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach) + ShrineOrchestrator.signerVerifier.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach) } override def adapterReport: String = { val report = for { adapterRequestHandler <- adapterOption } yield { val message = newBroadcastMessageWithRunQueryRequest import scala.concurrent.duration._ val (resultAttempt: Try[Result], elapsed: Duration) = { val start = System.currentTimeMillis val attempt = Try(adapterRequestHandler.handleRequest(message)) val end = System.currentTimeMillis (attempt, (end - start).milliseconds) } XmlUtil.stripWhitespace { { resultAttempt match { case scala.util.Failure(cause) => failureToXml(Failure(NodeId("Local"), cause)) case scala.util.Success(Result(origin, elapsed, response)) => { { origin } { elapsed } { response.toXml } } } } }.toString } report.getOrElse(notAnAdapter) } override def auditReport: String = { val report = for { auditDao <- auditDaoOption } yield { val recentEntries = auditDao.findRecentEntries(10) XmlUtil.stripWhitespace { { recentEntries map { entry => { entry.id } { entry.username } } } }.toString } report.getOrElse(notAHub) } override def queryReport: String = { val report = for { adapterDao <- adapterDaoOption } yield { val recentQueries = adapterDao.findRecentQueries(10) XmlUtil.stripWhitespace { { recentQueries.map { query => { query.networkId } { query.dateCreated } { query.name } } } }.toString } report.getOrElse(notAnAdapter) } override def versionReport: String = XmlUtil.stripWhitespace { { Versions.version } { ontologyMetadata.ontologyVersion } { adapterMappings.map(_.version).getOrElse("No adapter mappings present") } { Versions.scmRevision } { Versions.scmBranch } { Versions.buildDate } }.toString override def all: String = { s"$versionReport$keystoreReport$routingReport$hiveReport$networkReport$adapterReport$auditReport$queryReport" } } diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala index 55a5c1890..6f0ccbd46 100644 --- a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala +++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala @@ -1,337 +1,334 @@ package net.shrine.wiring import javax.sql.DataSource import com.typesafe.config.{Config, ConfigFactory} import net.shrine.adapter.dao.squeryl.tables.{Tables => AdapterTables} import net.shrine.adapter.dao.squeryl.{SquerylAdapterDao, SquerylI2b2AdminDao} import net.shrine.adapter.dao.{AdapterDao, I2b2AdminDao} import net.shrine.adapter.service.{AdapterConfig, AdapterRequestHandler, AdapterResource, AdapterService, I2b2AdminResource, I2b2AdminService} import net.shrine.adapter.translators.{ExpressionTranslator, QueryDefinitionTranslator} import net.shrine.adapter.{Adapter, AdapterMap, DeleteQueryAdapter, FlagQueryAdapter, ReadInstanceResultsAdapter, ReadPreviousQueriesAdapter, ReadQueryDefinitionAdapter, ReadQueryResultAdapter, ReadTranslatedQueryDefinitionAdapter, RenameQueryAdapter, RunQueryAdapter, UnFlagQueryAdapter} import net.shrine.authentication.Authenticator import net.shrine.authorization.QueryAuthorizationService import net.shrine.broadcaster.dao.HubDao import net.shrine.broadcaster.dao.squeryl.SquerylHubDao import net.shrine.broadcaster.service.{BroadcasterMultiplexerResource, BroadcasterMultiplexerService} import net.shrine.broadcaster.{AdapterClientBroadcaster, BroadcastAndAggregationService, NodeHandle, SigningBroadcastAndAggregationService} import net.shrine.client.{EndpointConfig, JerseyHttpClient, OntClient, Poster, PosterOntClient} import net.shrine.config.{DurationConfigParser, ConfigExtensions} import net.shrine.config.mappings.{AdapterMappings, AdapterMappingsSource, ClasspathFormatDetectingAdapterMappingsSource} import net.shrine.crypto.{DefaultSignerVerifier, KeyStoreCertCollection, KeyStoreDescriptorParser, TrustParam} import net.shrine.dao.squeryl.{DataSourceSquerylInitializer, SquerylDbAdapterSelecter, SquerylInitializer} import net.shrine.happy.{HappyShrineResource, HappyShrineService} import net.shrine.log.Loggable import net.shrine.ont.data.{OntClientOntologyMetadata, OntologyMetadata} import net.shrine.protocol.{HiveCredentials, NodeId, RequestType, ResultOutputType, ResultOutputTypes} import net.shrine.qep.dao.AuditDao import net.shrine.qep.dao.squeryl.SquerylAuditDao import net.shrine.qep.dao.squeryl.tables.{Tables => HubTables} import net.shrine.qep.{I2b2BroadcastResource, I2b2QepService, QepService, ShrineResource} import net.shrine.status.StatusJaxrs import org.squeryl.internals.DatabaseAdapter /** * @author clint * @since Jan 14, 2014 * * Application wiring for Shrine, in the base, non-HMS case. All vals are protecetd, so they may be accessed, * in subclasses without ballooning this class's public API, and lazy, to work around init-order surprises when * overriding vals declared inline. See * * https://stackoverflow.com/questions/15762650/scala-override-val-in-class-inheritance * * among other links mentioning val overrides, early initializers, etc. -Clint */ object ShrineOrchestrator extends ShrineJaxrsResources with Loggable { import NodeHandleSource.makeNodeHandles override def resources: Iterable[AnyRef] = { Seq(happyResource,statusJaxrs) ++ shrineResource ++ i2b2BroadcastResource ++ adapterResource ++ i2b2AdminResource ++ broadcasterMultiplexerResource } //todo another pass to put things only used in one place into that place's apply(Config) //Load config from file on the classpath called "shrine.conf" lazy val config: Config = ConfigFactory.load("shrine") val shrineConfig = config.getConfig("shrine") protected lazy val nodeId: NodeId = NodeId(shrineConfig.getString("humanReadableNodeName")) //TODO: Don't assume keystore lives on the filesystem, could come from classpath, etc - protected lazy val keyStoreDescriptor = shrineConfig.getConfigured("keystore",KeyStoreDescriptorParser(_)) - protected lazy val shrineCertCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFile(keyStoreDescriptor) + lazy val keyStoreDescriptor = shrineConfig.getConfigured("keystore",KeyStoreDescriptorParser(_)) + lazy val shrineCertCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFile(keyStoreDescriptor) protected lazy val keystoreTrustParam: TrustParam = TrustParam.SomeKeyStore(shrineCertCollection) //todo see if you can remove this by pushing it closer to where it gets used - protected lazy val signerVerifier: DefaultSignerVerifier = new DefaultSignerVerifier(shrineCertCollection) + lazy val signerVerifier: DefaultSignerVerifier = new DefaultSignerVerifier(shrineCertCollection) protected lazy val dataSource: DataSource = Jndi("java:comp/env/jdbc/shrineDB").get protected lazy val squerylAdapter: DatabaseAdapter = SquerylDbAdapterSelecter.determineAdapter(shrineConfig.getString("shrineDatabaseType")) protected lazy val squerylInitializer: SquerylInitializer = new DataSourceSquerylInitializer(dataSource, squerylAdapter) private lazy val pmEndpoint: EndpointConfig = shrineConfig.getConfigured("pmEndpoint", EndpointConfig(_)) protected lazy val pmPoster: Poster = Poster(shrineCertCollection,pmEndpoint) private lazy val ontEndpoint: EndpointConfig = shrineConfig.getConfigured("ontEndpoint", EndpointConfig(_)) protected lazy val ontPoster: Poster = Poster(shrineCertCollection,ontEndpoint) protected lazy val breakdownTypes: Set[ResultOutputType] = shrineConfig.getOptionConfigured("breakdownResultOutputTypes", ResultOutputTypes.fromConfig).getOrElse(Set.empty) protected lazy val hubDao: HubDao = new SquerylHubDao(squerylInitializer, new net.shrine.broadcaster.dao.squeryl.tables.Tables) lazy val crcHiveCredentials = shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.CRC)) //todo move as much of this block as possible to the adapter project, and get rid of this multi-assignment of one thing protected lazy val ( adapterService: Option[AdapterService], i2b2AdminService: Option[I2b2AdminService], adapterDao: Option[AdapterDao], adapterMappings: Option[AdapterMappings] ) = adapterComponentsToTuple(shrineConfig.getOptionConfiguredIf("adapter", AdapterConfig(_)).map { adapterConfig => //todo unwind adapterConfig and just have an adapter val crcEndpoint: EndpointConfig = adapterConfig.crcEndpoint val crcPoster: Poster = Poster(shrineCertCollection,crcEndpoint) val squerylAdapterTables: AdapterTables = new AdapterTables val adapterDao: AdapterDao = new SquerylAdapterDao(squerylInitializer, squerylAdapterTables)(breakdownTypes) //NB: Is i2b2HiveCredentials.projectId the right project id to use? val i2b2AdminDao: I2b2AdminDao = new SquerylI2b2AdminDao(crcHiveCredentials.projectId, squerylInitializer, squerylAdapterTables) val adapterMappingsSource: AdapterMappingsSource = ClasspathFormatDetectingAdapterMappingsSource(adapterConfig.adapterMappingsFileName) //NB: Fail fast val adapterMappings: AdapterMappings = adapterMappingsSource.load.get val expressionTranslator: ExpressionTranslator = ExpressionTranslator(adapterMappings) val queryDefinitionTranslator: QueryDefinitionTranslator = new QueryDefinitionTranslator(expressionTranslator) val doObfuscation = adapterConfig.setSizeObfuscation val runQueryAdapter = new RunQueryAdapter( crcPoster, adapterDao, crcHiveCredentials, queryDefinitionTranslator, adapterConfig.adapterLockoutAttemptsThreshold, doObfuscation, adapterConfig.immediatelyRunIncomingQueries, breakdownTypes, collectAdapterAudit = adapterConfig.collectAdapterAudit ) val readInstanceResultsAdapter: Adapter = new ReadInstanceResultsAdapter( crcPoster, crcHiveCredentials, adapterDao, doObfuscation, breakdownTypes, collectAdapterAudit = adapterConfig.collectAdapterAudit ) val readQueryResultAdapter: Adapter = new ReadQueryResultAdapter( crcPoster, crcHiveCredentials, adapterDao, doObfuscation, breakdownTypes, collectAdapterAudit = adapterConfig.collectAdapterAudit ) val readPreviousQueriesAdapter: Adapter = new ReadPreviousQueriesAdapter(adapterDao) val deleteQueryAdapter: Adapter = new DeleteQueryAdapter(adapterDao) val renameQueryAdapter: Adapter = new RenameQueryAdapter(adapterDao) val readQueryDefinitionAdapter: Adapter = new ReadQueryDefinitionAdapter(adapterDao) val readTranslatedQueryDefinitionAdapter: Adapter = new ReadTranslatedQueryDefinitionAdapter(nodeId, queryDefinitionTranslator) val flagQueryAdapter: Adapter = new FlagQueryAdapter(adapterDao) val unFlagQueryAdapter: Adapter = new UnFlagQueryAdapter(adapterDao) val adapterMap = AdapterMap(Map( RequestType.QueryDefinitionRequest -> runQueryAdapter, RequestType.GetRequestXml -> readQueryDefinitionAdapter, RequestType.UserRequest -> readPreviousQueriesAdapter, RequestType.InstanceRequest -> readInstanceResultsAdapter, RequestType.MasterDeleteRequest -> deleteQueryAdapter, RequestType.MasterRenameRequest -> renameQueryAdapter, RequestType.GetQueryResult -> readQueryResultAdapter, RequestType.ReadTranslatedQueryDefinitionRequest -> readTranslatedQueryDefinitionAdapter, RequestType.FlagQueryRequest -> flagQueryAdapter, RequestType.UnFlagQueryRequest -> unFlagQueryAdapter)) AdapterComponents( new AdapterService(nodeId, signerVerifier, adapterConfig.maxSignatureAge, adapterMap), new I2b2AdminService(adapterDao, i2b2AdminDao, pmPoster, runQueryAdapter), adapterDao, adapterMappings) }) val shouldQuerySelf = "hub.shouldQuerySelf" lazy val localAdapterServiceOption: Option[AdapterRequestHandler] = if(shrineConfig.getOption(shouldQuerySelf,_.getBoolean).getOrElse(false)) { //todo give this a default value (of false) in the reference.conf for the Hub, and make it part of the Hub's apply(config) require(adapterService.isDefined, s"Self-querying requested because shrine.$shouldQuerySelf is true, but this node is not configured to have an adapter") adapterService } else None //todo eventually make this just another downstream node accessed via loopback //todo anything that uses hubConfig should be inside the big Hub component. broadcastDesitnations leak out because the QEP doesn't use loopback to talk to itself. //todo as an easy earlier step, make the hub first, then the QEP. Ask the hub for its destinations val hubConfig = shrineConfig.getConfig("hub") //todo use an empty Set instead of Option[Set] private lazy val broadcastDestinations: Option[Set[NodeHandle]] = { if(hubConfig.getBoolean("create")) { Some(makeNodeHandles(hubConfig, keystoreTrustParam, nodeId, localAdapterServiceOption, breakdownTypes)) } else None } //todo a hub component. Gather them all up private lazy val broadcasterOption: Option[AdapterClientBroadcaster] = { if(hubConfig.getBoolean("create")) { require(broadcastDestinations.isDefined, "This node is configured to be a hub, but no downstream nodes are defined") Some(AdapterClientBroadcaster(broadcastDestinations.get, hubDao)) } else None } //todo a hub component lazy val broadcasterMultiplexerService: Option[BroadcasterMultiplexerService] = broadcasterOption.map(BroadcasterMultiplexerService(_, hubConfig.getConfigured("maxQueryWaitTime",DurationConfigParser(_)))) //todo anything that requires qepConfig should be inside QueryEntryPointComponents's apply protected lazy val qepConfig = shrineConfig.getConfig("queryEntryPoint") protected lazy val queryEntryPointComponents:Option[QueryEntryPointComponents] = if(qepConfig.getBoolean("create")) { val commonName: String = shrineCertCollection.myCommonName.getOrElse { val hostname = java.net.InetAddress.getLocalHost.getHostName warn(s"No common name available from ${shrineCertCollection.descriptor}. Using $hostname instead.") hostname } val broadcastService: BroadcastAndAggregationService = SigningBroadcastAndAggregationService( qepConfig, shrineCertCollection, breakdownTypes, broadcastDestinations, hubDao ) val auditDao: AuditDao = new SquerylAuditDao(squerylInitializer, new HubTables) val authenticator: Authenticator = AuthStrategy.determineAuthenticator(qepConfig, pmPoster) val authorizationService: QueryAuthorizationService = AuthStrategy.determineQueryAuthorizationService(qepConfig,authenticator) debug(s"authorizationService set to $authorizationService") Some(QueryEntryPointComponents( qepConfig, commonName, auditDao, authenticator, authorizationService, broadcastService )) } else None protected lazy val pmUrlString: String = pmEndpoint.url.toString protected lazy val ontologyMetadata: OntologyMetadata = { import scala.concurrent.duration._ //TODO: XXX: Un-hard-code max wait time param val ontClient: OntClient = new PosterOntClient(shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.ONT)), 1.minute, ontPoster) new OntClientOntologyMetadata(ontClient) } //TODO: Don't assume we're an adapter with an AdapterMappings (don't call .get) protected lazy val happyService: HappyShrineService = { new HappyShrineService( - keystoreDescriptor = keyStoreDescriptor, - certCollection = shrineCertCollection, - signer = signerVerifier, pmPoster = pmPoster, ontologyMetadata = ontologyMetadata, adapterMappings = adapterMappings, auditDaoOption = queryEntryPointComponents.map(_.auditDao), adapterDaoOption = adapterDao, adapterOption = adapterService ) } protected lazy val happyResource: HappyShrineResource = new HappyShrineResource(happyService) protected lazy val statusJaxrs: StatusJaxrs = StatusJaxrs(config) protected lazy val shrineResource: Option[ShrineResource] = queryEntryPointComponents.map(x => ShrineResource(x.shrineService)) protected lazy val i2b2BroadcastResource: Option[I2b2BroadcastResource] = queryEntryPointComponents.map(x => new I2b2BroadcastResource(x.i2b2Service,breakdownTypes)) protected lazy val adapterResource: Option[AdapterResource] = adapterService.map(AdapterResource(_)) protected lazy val i2b2AdminResource: Option[I2b2AdminResource] = i2b2AdminService.map(I2b2AdminResource(_, breakdownTypes)) protected lazy val broadcasterMultiplexerResource: Option[BroadcasterMultiplexerResource] = broadcasterMultiplexerService.map(BroadcasterMultiplexerResource(_)) //todo here's the Adapter. Move to the adapter package. private final case class AdapterComponents(adapterService: AdapterService, i2b2AdminService: I2b2AdminService, adapterDao: AdapterDao, adapterMappings: AdapterMappings) //todo here's the QEP. Move to the QEP package. case class QueryEntryPointComponents(shrineService: QepService, i2b2Service: I2b2QepService, auditDao: AuditDao) //todo auditDao is only used by the happy service to grab the most recent entries object QueryEntryPointComponents { def apply( qepConfig:Config, commonName: String, auditDao: AuditDao, authenticator: Authenticator, authorizationService: QueryAuthorizationService, broadcastService: BroadcastAndAggregationService ):QueryEntryPointComponents = { QueryEntryPointComponents( QepService( qepConfig, commonName, auditDao, authenticator, authorizationService, broadcastService, breakdownTypes ), I2b2QepService( qepConfig, commonName, auditDao, authenticator, authorizationService, broadcastService, breakdownTypes ), auditDao) } } //todo get rid of this private def adapterComponentsToTuple(option: Option[AdapterComponents]): (Option[AdapterService], Option[I2b2AdminService], Option[AdapterDao], Option[AdapterMappings]) = option match { case None => (None, None, None, None) case Some(AdapterComponents(a, b, c, d)) => (Option(a), Option(b), Option(c), Option(d)) } def poster(keystoreCertCollection: KeyStoreCertCollection)(endpoint: EndpointConfig): Poster = { val httpClient = JerseyHttpClient(keystoreCertCollection, endpoint) Poster(endpoint.url.toString, httpClient) } }