diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b83002d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+
+/data
diff --git a/README.md b/README.md
index 2bd51ff..dfd142c 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-# indy-client-modules
\ No newline at end of file
+# indy-client-modules
+
+Provide the client modules to Indy functionalities access and resources management on contents, stores, promotion, folo tracking, koji, etc.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d7fb382
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,156 @@
+
+
+
+ 4.0.0
+
+
+ org.commonjava
+ commonjava
+ 17
+
+
+ org.commonjava.indy
+ indy-client-modules
+ 1.0.0-SNAPSHOT
+
+ Indy :: Client :: Modules
+
+
+ scm:git:https://github.com/commonjava/indy-client-modules
+ scm:git:https://github.com/commonjava/indy-client-modules
+ http://github.com/Commonjava/indy-client-modules
+ HEAD
+
+
+
+ 1.12
+ 1.8
+ 4.5.9
+ 1.7.36
+ 2.11.0
+ 1.6.6
+
+
+
+
+
+ org.commonjava.util
+ jhttpc
+ ${jhttpcVersion}
+
+
+ org.commonjava.util
+ o11yphant-trace-api
+ ${o11yphantVersion}
+
+
+ org.commonjava.util
+ o11yphant-trace-honeycomb
+ ${o11yphantVersion}
+
+
+ org.commonjava.util
+ o11yphant-trace-otel
+ ${o11yphantVersion}
+
+
+ org.commonjava.util
+ o11yphant-trace-helper-jhttpc
+ ${o11yphantVersion}
+
+
+ org.commonjava.util
+ o11yphant-metrics-common
+ ${o11yphantVersion}
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclientVersion}
+
+
+ org.slf4j
+ jcl-over-slf4j
+ ${slf4jVersion}
+
+
+ commons-io
+ commons-io
+ ${commonsioVersion}
+
+
+ io.swagger
+ swagger-annotations
+ ${swaggerVersion}
+ provided
+
+
+
+
+
+
+ org.commonjava.util
+ jhttpc
+
+
+ org.commonjava.util
+ o11yphant-trace-api
+
+
+ org.commonjava.util
+ o11yphant-trace-honeycomb
+
+
+ io.undertow
+ undertow-servlet
+
+
+
+
+ org.commonjava.util
+ o11yphant-trace-otel
+
+
+ org.commonjava.util
+ o11yphant-trace-helper-jhttpc
+
+
+ org.commonjava.util
+ o11yphant-metrics-common
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.slf4j
+ jcl-over-slf4j
+
+
+ commons-io
+ commons-io
+
+
+ io.swagger
+ swagger-annotations
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/commonjava/indy/client/core/Indy.java b/src/main/java/org/commonjava/indy/client/core/Indy.java
new file mode 100644
index 0000000..a23d3ff
--- /dev/null
+++ b/src/main/java/org/commonjava/indy/client/core/Indy.java
@@ -0,0 +1,391 @@
+/**
+ * Copyright (C) 2011-2022 Red Hat, Inc. (https://github.com/Commonjava/indy)
+ *
+ * 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 org.commonjava.indy.client.core;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.commonjava.indy.client.modules.IndyContentClientModule;
+import org.commonjava.indy.client.modules.IndyStoresClientModule;
+import org.commonjava.o11yphant.trace.TracerConfiguration;
+import org.commonjava.util.jhttpc.auth.PasswordManager;
+import org.commonjava.util.jhttpc.model.SiteConfig;
+import java.io.Closeable;
+import java.util.*;
+
+@SuppressWarnings( "unused" )
+public class Indy
+ implements Closeable
+{
+
+ public static final String HEADER_COMPONENT_ID = "component-id";
+
+ private String apiVersion;
+
+ private IndyClientHttp http;
+
+ private final Set moduleRegistry;
+
+ @Deprecated
+ public Indy(final String baseUrl, final IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this( null, null, Arrays.asList( modules ), IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final String baseUrl, final IndyClientAuthenticator authenticator, final IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this( authenticator, null, Arrays.asList( modules ), IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy( final String baseUrl, final ObjectMapper mapper, final IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this( null, mapper, Arrays.asList( modules ), IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final String baseUrl, final IndyClientAuthenticator authenticator, final ObjectMapper mapper,
+ final IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this( authenticator, mapper, Arrays.asList( modules ), IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final String baseUrl, final Collection modules )
+ throws IndyClientException
+ {
+ this( null, null, modules, IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final String baseUrl, final IndyClientAuthenticator authenticator,
+ final Collection modules )
+ throws IndyClientException
+ {
+ this( authenticator, null, modules, IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final String baseUrl, final ObjectMapper mapper, final Collection modules )
+ throws IndyClientException
+ {
+ this( null, mapper, modules, IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final String baseUrl, final IndyClientAuthenticator authenticator, final ObjectMapper mapper,
+ final Collection modules )
+ throws IndyClientException
+ {
+ this( authenticator, mapper, modules, IndyClientHttp.defaultSiteConfig( baseUrl ) );
+ }
+
+ @Deprecated
+ public Indy(final IndyClientAuthenticator authenticator, final ObjectMapper mapper,
+ final Collection modules, SiteConfig location )
+ throws IndyClientException
+ {
+ this( location, authenticator, mapper,
+ modules == null ? new IndyClientModule[0] : modules.toArray( new IndyClientModule[0] ) );
+ }
+
+ @Deprecated
+ public Indy(final SiteConfig location, final IndyClientAuthenticator authenticator, final ObjectMapper mapper,
+ final IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this( location, authenticator, mapper, Collections.emptyMap(), modules );
+ }
+
+ /**
+ *
+ * @param location -
+ * @param authenticator -
+ * @param mapper -
+ * @param mdcCopyMappings a map of fields to copy from LoggingMDC to http request headers where key=MDCMey and value=headerName
+ * @param modules -
+ * @throws IndyClientException -
+ * @deprecated - since 3.1.0, we have new {@link Builder} to set up the indy client, so please use it instead in future
+ */
+ @Deprecated
+ public Indy(final SiteConfig location, final IndyClientAuthenticator authenticator, final ObjectMapper mapper,
+ final Map mdcCopyMappings, final IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this.http = new IndyClientHttp( authenticator, mapper == null ? new ObjectMapper() : mapper, location,
+ getApiVersion(), mdcCopyMappings );
+
+ this.moduleRegistry = new HashSet<>();
+
+ setupStandardModules();
+ for ( final IndyClientModule module : modules )
+ {
+ module.setup( this, http );
+ moduleRegistry.add( module );
+ }
+
+ }
+
+ /**
+ * @deprecated - since 3.1.0, we have new {@link Builder} to set up the indy client, so please use it instead in future
+ */
+ @Deprecated
+ public Indy(SiteConfig location, PasswordManager passwordManager, IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this( location, passwordManager, null, modules );
+ }
+
+ /**
+ * @deprecated - since 3.1.0, we have new {@link Builder} to set up the indy client, so please use it instead in future
+ */
+ @Deprecated
+ public Indy(SiteConfig location, PasswordManager passwordManager, ObjectMapper objectMapper,
+ IndyClientModule... modules )
+ throws IndyClientException
+ {
+ this.http =
+ new IndyClientHttp( passwordManager, objectMapper == null ? new ObjectMapper() : objectMapper,
+ location, getApiVersion() );
+
+ this.moduleRegistry = new HashSet<>();
+
+ setupStandardModules();
+ for ( final IndyClientModule module : modules )
+ {
+ module.setup( this, http );
+ moduleRegistry.add( module );
+ }
+ }
+
+ private Indy(final Set modules )
+ {
+ this.moduleRegistry = new HashSet<>();
+ moduleRegistry.addAll( modules );
+ }
+
+ public static Builder builder()
+ {
+ return new Builder();
+ }
+
+ public static final class Builder
+ {
+ private Set moduleRegistry;
+
+ private PasswordManager passwordManager;
+
+ private SiteConfig location;
+
+ private ObjectMapper objectMapper;
+
+ private IndyClientAuthenticator authenticator;
+
+ private Map mdcCopyMappings;
+
+ private TracerConfiguration existedTraceConfig;
+
+ private Builder()
+ {
+ }
+
+ public Builder setModules( Set modules )
+ {
+ this.moduleRegistry = modules;
+ return this;
+ }
+
+ public Builder setModules( IndyClientModule... modules )
+ {
+ this.moduleRegistry = new HashSet<>();
+ Collections.addAll( moduleRegistry, modules );
+ return this;
+ }
+
+ public Builder setPasswordManager( PasswordManager passwordManager )
+ {
+ this.passwordManager = passwordManager;
+ return this;
+ }
+
+ public Builder setLocation( SiteConfig location )
+ {
+ this.location = location;
+ return this;
+ }
+
+ public Builder setObjectMapper( ObjectMapper objectMapper )
+ {
+ this.objectMapper = objectMapper;
+ return this;
+ }
+
+ public Builder setExistedTraceConfig( TracerConfiguration existedTraceConfig )
+ {
+ this.existedTraceConfig = existedTraceConfig;
+ return this;
+ }
+
+ public Builder setAuthenticator( IndyClientAuthenticator authenticator )
+ {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ public Builder setMdcCopyMappings( Map mdcCopyMappings )
+ {
+ this.mdcCopyMappings = mdcCopyMappings;
+ return this;
+ }
+
+ public Indy build()
+ throws IndyClientException
+ {
+ Set modules = this.moduleRegistry == null ? new HashSet<>() : this.moduleRegistry;
+ final Indy indy = new Indy( modules );
+ if ( this.objectMapper == null )
+ {
+ this.objectMapper = new ObjectMapper();
+ }
+
+ indy.http = IndyClientHttp.builder()
+ .setAuthenticator( this.authenticator )
+ .setApiVersion( indy.getApiVersion() )
+ .setLocation( this.location )
+ .setPasswordManager( this.passwordManager )
+ .setExistedTraceConfig( this.existedTraceConfig )
+ .setMdcCopyMappings( this.mdcCopyMappings )
+ .setObjectMapper( this.objectMapper )
+ .build();
+ indy.setupStandardModules();
+ for ( final IndyClientModule module : this.moduleRegistry )
+ {
+ module.setup( indy, indy.http );
+ }
+
+ return indy;
+ }
+ }
+
+ public void setupExternal( final IndyClientModule module )
+ {
+ setup( module );
+ }
+
+ /**
+ * Not used since migration to jHTTPc library
+ */
+ @Deprecated
+ public Indy connect()
+ {
+ return this;
+ }
+
+ @Override
+ public void close()
+ {
+ http.close();
+ }
+
+ public IndyStoresClientModule stores()
+ throws IndyClientException
+ {
+ return module( IndyStoresClientModule.class );
+ }
+
+ public IndyContentClientModule content()
+ throws IndyClientException
+ {
+ return module( IndyContentClientModule.class );
+ }
+
+ public T module( final Class type )
+ throws IndyClientException
+ {
+ for ( final IndyClientModule module : moduleRegistry )
+ {
+ if ( type.isInstance( module ) )
+ {
+ return type.cast( module );
+ }
+ }
+
+ throw new IndyClientException( "Module not found: %s.", type.getName() );
+ }
+
+ public boolean hasModule( Class> type )
+ {
+ for ( final IndyClientModule module : moduleRegistry )
+ {
+ if ( type.isInstance( module ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public String getBaseUrl()
+ {
+ return http.getBaseUrl();
+ }
+
+ private void setupStandardModules()
+ {
+ final Set standardModules = new HashSet<>();
+ standardModules.add( new IndyStoresClientModule() );
+ standardModules.add( new IndyContentClientModule() );
+
+ for ( final IndyClientModule module : standardModules )
+ {
+ setup( module );
+ }
+ }
+
+ private void setup( IndyClientModule module )
+ {
+ module.setup( this, http );
+ moduleRegistry.add( module );
+
+ Iterable serMods = module.getSerializerModules();
+ if ( serMods != null )
+ {
+ http.getObjectMapper().registerModules( serMods );
+ }
+ }
+
+ // DA, Builder, Orchestrator, etc. If available, this will be sent as a request header.
+ public void setComponentId( String componentId )
+ {
+ http.addDefaultHeader( HEADER_COMPONENT_ID, componentId );
+ }
+
+ // Default headers will be sent along with each request
+ public void addDefaultHeader( String key, String value )
+ {
+ http.addDefaultHeader( key, value );
+ }
+
+ public String getApiVersion()
+ {
+ return apiVersion;
+ }
+}
diff --git a/src/main/java/org/commonjava/indy/client/core/IndyClientAuthenticator.java b/src/main/java/org/commonjava/indy/client/core/IndyClientAuthenticator.java
new file mode 100644
index 0000000..2017681
--- /dev/null
+++ b/src/main/java/org/commonjava/indy/client/core/IndyClientAuthenticator.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2011-2022 Red Hat, Inc. (https://github.com/Commonjava/indy)
+ *
+ * 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 org.commonjava.indy.client.core;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.commonjava.util.jhttpc.JHttpCException;
+import org.commonjava.util.jhttpc.auth.ClientAuthenticator;
+import org.commonjava.util.jhttpc.auth.PasswordType;
+import org.commonjava.util.jhttpc.model.SiteConfig;
+
+public abstract class IndyClientAuthenticator extends ClientAuthenticator
+{
+ @Override
+ public HttpClientContext decoratePrototypeContext(AuthScope scope, SiteConfig location, PasswordType type, HttpClientContext ctx)
+ throws JHttpCException
+ {
+ return ctx;
+ }
+
+ @Override
+ public HttpClientBuilder decorateClientBuilder(HttpClientBuilder builder)
+ throws JHttpCException
+ {
+ return builder;
+ }
+
+}
diff --git a/src/main/java/org/commonjava/indy/client/core/IndyClientException.java b/src/main/java/org/commonjava/indy/client/core/IndyClientException.java
new file mode 100644
index 0000000..df1d1f2
--- /dev/null
+++ b/src/main/java/org/commonjava/indy/client/core/IndyClientException.java
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2011-2022 Red Hat, Inc. (https://github.com/Commonjava/indy)
+ *
+ * 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 org.commonjava.indy.client.core;
+
+
+import java.io.NotSerializableException;
+import java.io.Serializable;
+import java.text.MessageFormat;
+
+/**
+ * Signals an error communicating with the Indy server.
+ * @author jdcasey
+ */
+public class IndyClientException
+ extends IndyException
+{
+ private Object[] params;
+
+ private transient String formattedMessage;
+
+ private final int statusCode;
+
+ public IndyClientException( final int statusCode, final String message, final Object... params )
+ {
+ super( message );
+ this.statusCode = statusCode;
+ this.params = params;
+ }
+
+ public IndyClientException( final int statusCode, final String message, final Throwable cause,
+ final Object... params )
+ {
+ super( message, cause );
+ this.statusCode = statusCode;
+ this.params = params;
+ }
+
+ public IndyClientException( final String message, final Object... params )
+ {
+ super( message );
+ this.params = params;
+ this.statusCode = -1;
+ }
+
+ public IndyClientException( final String message, final Throwable cause, final Object... params )
+ {
+ super( message, cause );
+ this.params = params;
+ this.statusCode = -1;
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public synchronized String getMessage()
+ {
+ if ( formattedMessage == null )
+ {
+ final String format = super.getMessage();
+ if ( params == null || params.length < 1 )
+ {
+ formattedMessage = format;
+ }
+ else
+ {
+ final String original = formattedMessage;
+ try
+ {
+ formattedMessage = String.format( format.replaceAll( "\\{\\}", "%s" ), params );
+ }
+ catch ( final Error e )
+ {
+ }
+ catch ( final RuntimeException e )
+ {
+ }
+ catch ( final Exception e )
+ {
+ }
+
+ if ( formattedMessage == null || original == formattedMessage )
+ {
+ try
+ {
+ formattedMessage = MessageFormat.format( format, params );
+ }
+ catch ( final Error e )
+ {
+ formattedMessage = format;
+ throw e;
+ }
+ catch ( final RuntimeException e )
+ {
+ formattedMessage = format;
+ throw e;
+ }
+ catch ( final Exception e )
+ {
+ formattedMessage = format;
+ }
+ }
+ }
+ }
+
+ return formattedMessage;
+ }
+
+ /**
+ * Stringify all parameters pre-emptively on serialization, to prevent {@link NotSerializableException}.
+ * Since all parameters are used in {@link String#format} or {@link MessageFormat#format}, flattening them
+ * to strings is an acceptable way to provide this functionality without making the use of {@link Serializable}
+ * viral.
+ */
+ private Object writeReplace()
+ {
+ final Object[] newParams = new Object[params.length];
+ int i = 0;
+ for ( final Object object : params )
+ {
+ newParams[i] = String.valueOf( object );
+ i++;
+ }
+
+ this.params = newParams;
+ return this;
+ }
+
+ public int getStatusCode()
+ {
+ return statusCode;
+ }
+
+}
diff --git a/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java b/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java
new file mode 100644
index 0000000..24c610c
--- /dev/null
+++ b/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java
@@ -0,0 +1,1141 @@
+/**
+ * Copyright (C) 2011-2022 Red Hat, Inc. (https://github.com/Commonjava/indy)
+ *
+ * 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 org.commonjava.indy.client.core;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.*;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.util.VersionInfo;
+import org.commonjava.indy.client.helper.HttpResources;
+import org.commonjava.indy.client.metric.ClientMetricManager;
+import org.commonjava.indy.client.metric.ClientMetrics;
+import org.commonjava.o11yphant.jhttpc.SpanningHttpFactory;
+import org.commonjava.o11yphant.trace.TracerConfiguration;
+import org.commonjava.util.jhttpc.HttpFactory;
+import org.commonjava.util.jhttpc.HttpFactoryIfc;
+import org.commonjava.util.jhttpc.JHttpCException;
+import org.commonjava.util.jhttpc.auth.PasswordManager;
+import org.commonjava.util.jhttpc.model.SiteConfig;
+import org.commonjava.util.jhttpc.model.SiteConfigBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import java.util.function.Supplier;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.commonjava.indy.client.helper.HttpResources.cleanupResources;
+import static org.commonjava.indy.client.helper.HttpResources.entityToString;
+import static org.commonjava.indy.client.metric.ClientMetricConstants.HEADER_CLIENT_API;
+import static org.commonjava.indy.client.metric.ClientMetricConstants.HEADER_CLIENT_TRACE_ID;
+import static org.commonjava.indy.client.util.UrlUtils.buildUrl;
+
+@SuppressWarnings( "unused" )
+public class IndyClientHttp
+ implements Closeable
+{
+ public static final int GLOBAL_MAX_CONNECTIONS = 20;
+
+ public static final String CHECK_CACHE_ONLY = "cache-only";
+
+ public final static String HEADER_INDY_API_VERSION = "Indy-API-Version"; // the API version for the requester
+
+ private final Logger logger = LoggerFactory.getLogger( getClass() );
+
+ private final ObjectMapper objectMapper;
+
+ private final SiteConfig location;
+
+ private HttpFactoryIfc factory;
+
+ private final String baseUrl;
+
+ private List defaultHeaders;
+
+ private Map mdcCopyMappings = new HashMap<>();
+
+ private ClientMetricManager metricManager;
+
+ /**
+ *
+ * @param authenticator -
+ * @param mapper -
+ * @param location -
+ * @param apiVersion -
+ * @param mdcCopyMappings a map of fields to copy from LoggingMDC to http request headers where key=MDCMey and value=headerName
+ * @throws IndyClientException -
+ * @deprecated - since 3.1.0, we have introduced new {@link Builder} to set this up, so please try to use it
+ */
+ @Deprecated
+ public IndyClientHttp(final IndyClientAuthenticator authenticator, final ObjectMapper mapper,
+ SiteConfig location, String apiVersion, Map mdcCopyMappings )
+ throws IndyClientException
+ {
+ this( mapper, location, apiVersion );
+ this.mdcCopyMappings = mdcCopyMappings;
+ metricManager = new ClientMetricManager( location );
+ factory = new SpanningHttpFactory( new HttpFactory( authenticator ), metricManager.getTraceManager() );
+ }
+
+ /**
+ * @deprecated - since 3.1.0, we have introduced new {@link Builder} to set this up, so please try to use it
+ */
+ @Deprecated
+ public IndyClientHttp(final PasswordManager passwordManager, final ObjectMapper mapper, SiteConfig location,
+ String apiVersion )
+ throws IndyClientException
+ {
+ this( mapper, location, apiVersion );
+
+ metricManager = new ClientMetricManager( location );
+ factory = new SpanningHttpFactory( new HttpFactory( passwordManager ), metricManager.getTraceManager() );
+ }
+
+ private IndyClientHttp(final ObjectMapper mapper, SiteConfig location, String apiVersion )
+ throws IndyClientException
+ {
+ this.objectMapper = mapper;
+ this.location = location;
+ baseUrl = location.getUri();
+ checkBaseUrl( baseUrl );
+ addApiVersionHeader( apiVersion );
+ initUserAgent( apiVersion );
+ }
+
+ public static Builder builder()
+ {
+ return new Builder();
+ }
+
+ public static final class Builder
+ {
+ private PasswordManager passwordManager;
+
+ private IndyClientAuthenticator authenticator;
+
+ private ObjectMapper objectMapper;
+
+ private SiteConfig location;
+
+ private String apiVersion;
+
+ private TracerConfiguration existedTraceConfig;
+
+ private Map mdcCopyMappings;
+
+ private Builder()
+ {
+ }
+
+ public Builder setPasswordManager( PasswordManager passwordManager )
+ {
+ this.passwordManager = passwordManager;
+ return this;
+ }
+
+ public Builder setAuthenticator( IndyClientAuthenticator authenticator )
+ {
+ this.authenticator = authenticator;
+ return this;
+ }
+
+ public Builder setObjectMapper( ObjectMapper objectMapper )
+ {
+ this.objectMapper = objectMapper;
+ return this;
+ }
+
+ public Builder setLocation( SiteConfig location )
+ {
+ this.location = location;
+ return this;
+ }
+
+ public Builder setApiVersion( String apiVersion )
+ {
+ this.apiVersion = apiVersion;
+ return this;
+ }
+
+ public Builder setExistedTraceConfig( TracerConfiguration existedTraceConfig )
+ {
+ this.existedTraceConfig = existedTraceConfig;
+ return this;
+ }
+
+
+ public Builder setMdcCopyMappings( Map mdcCopyMappings )
+ {
+ this.mdcCopyMappings = mdcCopyMappings;
+ return this;
+ }
+
+ public IndyClientHttp build()
+ throws IndyClientException
+ {
+ if ( StringUtils.isBlank( this.apiVersion ) )
+ {
+ throw new IllegalArgumentException( "Missing API version!" );
+ }
+ if ( this.objectMapper == null )
+ {
+ throw new IllegalArgumentException( "Missing ObjectMapper!" );
+ }
+ if ( this.location == null )
+ {
+ throw new IllegalArgumentException( "Missing SiteConfig for setting configurations!" );
+ }
+
+ final IndyClientHttp client = new IndyClientHttp( this.objectMapper, this.location, this.apiVersion );
+
+ if ( this.mdcCopyMappings != null && !this.mdcCopyMappings.isEmpty() )
+ {
+ client.mdcCopyMappings = this.mdcCopyMappings;
+ }
+
+ HttpFactory factory;
+ if ( this.authenticator != null )
+ {
+ factory = new HttpFactory( this.authenticator );
+ }
+ else
+ {
+ factory = new HttpFactory( this.passwordManager );
+ }
+
+ ClientMetricManager metricManager;
+ if ( this.existedTraceConfig != null )
+ {
+ metricManager = new ClientMetricManager( this.existedTraceConfig );
+ }
+ else
+ {
+ metricManager = new ClientMetricManager( location );
+ }
+
+ client.metricManager = metricManager;
+ client.factory = new SpanningHttpFactory( factory, metricManager.getTraceManager() );
+
+ return client;
+ }
+ }
+
+ private void initUserAgent( final String apiVersion )
+ {
+ String hcUserAgent =
+ VersionInfo.getUserAgent( "Apache-HttpClient", "org.apache.http.client", HttpClientBuilder.class );
+
+ addDefaultHeader( "User-Agent",
+ String.format( "Indy (api: %s) via %s", apiVersion, hcUserAgent ) );
+ }
+
+ private String addClientTraceHeader()
+ {
+ String traceId = UUID.randomUUID().toString();
+ addDefaultHeader( HEADER_CLIENT_TRACE_ID, traceId );
+ addDefaultHeader( HEADER_CLIENT_API, String.valueOf( true ) );
+ return traceId;
+ }
+
+ private void addApiVersionHeader( String apiVersion )
+ {
+ if ( isNotBlank( apiVersion ) )
+ {
+ addDefaultHeader( HEADER_INDY_API_VERSION, apiVersion );
+ }
+ }
+
+ private void checkBaseUrl( String baseUrl )
+ throws IndyClientException
+ {
+ try
+ {
+ new URL( baseUrl );
+ }
+ catch ( final MalformedURLException e )
+ {
+ throw new IndyClientException( "Invalid base-url: {}", e, baseUrl );
+ }
+ }
+
+ /**
+ * Not used since migration to jHTTPc library
+ */
+ @Deprecated
+ public void connect( final HttpClientConnectionManager connectionManager )
+ {
+ // NOP, now that we've moved to HttpFactory.
+ }
+
+ /**
+ * Not used since migration to jHTTPc library
+ */
+ @Deprecated
+ public synchronized void connect()
+ {
+ // NOP, now that we've moved to HttpFactory.
+ }
+
+ public Map head( final String path )
+ throws IndyClientException
+ {
+ return head( path, HttpStatus.SC_OK );
+ }
+
+ public Map head( final String path, final int... responseCodes )
+ throws IndyClientException
+ {
+ HttpHead request = newJsonHead( buildUrl( baseUrl, path ) );
+
+ connect();
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ ClientMetrics metrics = metricManager.register( request );
+ try
+ {
+ addLoggingMDCToHeaders( request );
+ client = newClient();
+ response = client.execute( request, newContext() );
+
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ if ( sl.getStatusCode() == HttpStatus.SC_NOT_FOUND )
+ {
+ return null;
+ }
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error executing HEAD: %s. Status was: %d %s (%s)",
+ path, sl.getStatusCode(), sl.getReasonPhrase(),
+ sl.getProtocolVersion() );
+ }
+
+ final Map headers = new HashMap<>();
+ for ( final Header header : response.getAllHeaders() )
+ {
+ final String name = header.getName().toLowerCase();
+
+ if ( !headers.containsKey( name ) )
+ {
+ headers.put( name, header.getValue() );
+ }
+ }
+
+ return headers;
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( request, response, client, metrics );
+ }
+ }
+
+ public T get( final String path, final Class type )
+ throws IndyClientException
+ {
+ HttpGet request = newJsonGet( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( request );
+
+ connect();
+
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( request );
+ response = client.execute( request, newContext() );
+
+ final StatusLine sl = response.getStatusLine();
+
+ if ( sl.getStatusCode() != 200 )
+ {
+ if ( sl.getStatusCode() == 404 )
+ {
+ return null;
+ }
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error retrieving %s from: %s.\n%s",
+ type.getSimpleName(), path, new IndyResponseErrorDetails( response ) );
+ }
+
+ final String json = entityToString( response );
+ logger.debug( "Got JSON:\n\n{}\n\n", json );
+ final T value = objectMapper.readValue( json, type );
+
+ logger.debug( "Got result object: {}", value );
+
+ return value;
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( request, response, client, metrics );
+ }
+ }
+
+ public T get( final String path, final TypeReference typeRef )
+ throws IndyClientException
+ {
+ HttpGet request = newJsonGet( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( request );
+
+ connect();
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( request );
+ response = client.execute( request, newContext() );
+ logger.trace( "Get request url path: {}, url host: {}", request.getURI().getPath(),
+ request.getURI().getHost() );
+ final StatusLine sl = response.getStatusLine();
+ if ( sl.getStatusCode() != 200 )
+ {
+ if ( sl.getStatusCode() == 404 )
+ {
+ return null;
+ }
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error retrieving %s from: %s.\n%s",
+ typeRef.getType(), path, new IndyResponseErrorDetails( response ) );
+ }
+
+ final String json = entityToString( response );
+
+ return objectMapper.readValue( json, typeRef );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( request, response, client, metrics );
+ }
+ }
+
+ public HttpResources getRaw( final HttpGet req )
+ throws IndyClientException
+ {
+ ClientMetrics metrics = metricManager.register( req );
+
+ connect();
+
+ addLoggingMDCToHeaders( req );
+ CloseableHttpResponse response = null;
+ try
+ {
+ final CloseableHttpClient client = newClient();
+
+ response = client.execute( req, newContext() );
+ return new HttpResources( req, response, client, metrics );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ // DO NOT CLOSE!!!! We're handing off control of the response to the caller!
+ // closeQuietly( response );
+ }
+ }
+
+ public HttpResources getRaw( final String path )
+ throws IndyClientException
+ {
+ return getRaw( path, Collections.singletonMap( "Accept", "*" ) );
+ }
+
+ public HttpResources getRaw( final String path, final Map headers )
+ throws IndyClientException
+ {
+ final HttpGet req = newRawGet( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( req );
+
+ connect();
+
+ CloseableHttpResponse response = null;
+ try
+ {
+ addLoggingMDCToHeaders( req );
+ if ( headers != null )
+ {
+ headers.forEach( req::setHeader );
+ }
+ final CloseableHttpClient client = newClient();
+
+ response = client.execute( req, newContext() );
+ return new HttpResources( req, response, client, metrics );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ // metrics.close();
+ // DO NOT CLOSE!!!! We're handing off control of the response to the caller!
+ // closeQuietly( response );
+ }
+ }
+
+ public void putWithStream( final String path, final InputStream stream )
+ throws IndyClientException
+ {
+ putWithStream( path, stream, HttpStatus.SC_CREATED );
+ }
+
+ public void putWithStream( final String path, final InputStream stream, final int... responseCodes )
+ throws IndyClientException
+ {
+ final HttpPut put = newRawPut( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( put );
+
+ connect();
+
+ addLoggingMDCToHeaders( put );
+ final CloseableHttpClient client = newClient();
+ CloseableHttpResponse response = null;
+ try
+ {
+ put.setEntity( new InputStreamEntity( stream ) );
+
+ response = client.execute( put, newContext() );
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ metrics.registerErr( sl );
+ throw new ClientProtocolException(
+ new IndyClientException( sl.getStatusCode(), "Error in response from: %s.\n%s", path,
+ new IndyResponseErrorDetails( response ) ) );
+ }
+
+ }
+ catch ( final ClientProtocolException e )
+ {
+ final Throwable cause = e.getCause();
+ metrics.registerErr( cause );
+ if ( cause instanceof IndyClientException )
+ {
+ throw (IndyClientException) cause;
+ }
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( put, response, client, metrics );
+ }
+ }
+
+ public boolean put( final String path, final Object value )
+ throws IndyClientException
+ {
+ return put( path, value, HttpStatus.SC_OK, HttpStatus.SC_CREATED );
+ }
+
+ public boolean put( final String path, final Object value, final int... responseCodes )
+ throws IndyClientException
+ {
+ HttpPut put = newJsonPut( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( put );
+
+ checkRequestValue( value );
+ connect();
+
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( put );
+
+ put.setEntity( new StringEntity( objectMapper.writeValueAsString( value ) ) );
+
+ response = client.execute( put, newContext() );
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error in response from: %s.\n%s", path,
+ new IndyResponseErrorDetails( response ) );
+ }
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( put, response, client, metrics );
+ }
+
+ return true;
+ }
+
+ public HttpResources execute( HttpRequestBase request )
+ throws IndyClientException
+ {
+ ClientMetrics metrics = metricManager.register( request );
+
+ connect();
+
+ addLoggingMDCToHeaders( request );
+ CloseableHttpResponse response = null;
+ try
+ {
+ final CloseableHttpClient client = newClient();
+
+ response = client.execute( request, newContext() );
+ return new HttpResources( request, response, client, metrics );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ // DO NOT CLOSE!!!! We're handing off control of the response to the caller!
+ // closeQuietly( response );
+ }
+ }
+
+ @SuppressWarnings( "UnusedReturnValue" )
+ public HttpResources postRaw( final String path, Object value )
+ throws IndyClientException
+ {
+ return postRaw( path, value, Collections.singletonMap( "Accept", "*" ) );
+ }
+
+ public HttpResources postRaw( final String path, Object value, final Map headers )
+ throws IndyClientException
+ {
+ final HttpPost req = newRawPost( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( req );
+
+ checkRequestValue( value );
+ connect();
+
+ CloseableHttpResponse response = null;
+ try
+ {
+ addLoggingMDCToHeaders( req );
+ if ( headers != null )
+ {
+ for ( String key : headers.keySet() )
+ {
+ req.setHeader( key, headers.get( key ) );
+ }
+ }
+
+ req.setEntity( new StringEntity( objectMapper.writeValueAsString( value ) ) );
+
+ final CloseableHttpClient client = newClient();
+
+ response = client.execute( req, newContext() );
+ return new HttpResources( req, response, client, metrics );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ // DO NOT CLOSE!!!! We're handing off control of the response to the caller!
+ // closeQuietly( response );
+ }
+ }
+
+ private void checkRequestValue( Object value )
+ throws IndyClientException
+ {
+ if ( value == null )
+ {
+ throw new IndyClientException( "Cannot use null request value!" );
+ }
+ }
+
+ public T postWithResponse( final String path, final Object value, final Class type )
+ throws IndyClientException
+ {
+ return postWithResponse( path, value, type, HttpStatus.SC_CREATED, HttpStatus.SC_OK );
+ }
+
+ public T postWithResponse( final String path, final Object value, final Class type,
+ final int... responseCodes )
+ throws IndyClientException
+ {
+ HttpPost post = newJsonPost( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( post );
+
+ checkRequestValue( value );
+
+ connect();
+
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( post );
+
+ post.setEntity( new StringEntity( objectMapper.writeValueAsString( value ) ) );
+
+ response = client.execute( post, newContext() );
+
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error POSTING with %s result from: %s.\n%s",
+ type.getSimpleName(), path, new IndyResponseErrorDetails( response ) );
+ }
+
+ final String json = entityToString( response );
+ return objectMapper.readValue( json, type );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( post, response, client, metrics );
+ }
+ }
+
+ public boolean validResponseCode( final int statusCode, final int[] responseCodes )
+ {
+ for ( final int code : responseCodes )
+ {
+ if ( code == statusCode )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public T postWithResponse( final String path, final Object value, final TypeReference typeRef )
+ throws IndyClientException
+ {
+ return postWithResponse( path, value, typeRef, HttpStatus.SC_CREATED );
+ }
+
+ public T postWithResponse( final String path, final Object value, final TypeReference typeRef,
+ final int... responseCodes )
+ throws IndyClientException
+ {
+ HttpPost post = newJsonPost( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( post );
+
+ checkRequestValue( value );
+
+ connect();
+
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( post );
+
+ post.setEntity( new StringEntity( objectMapper.writeValueAsString( value ) ) );
+
+ response = client.execute( post, newContext() );
+
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error retrieving %s from: %s.\n%s",
+ typeRef.getType(), path, new IndyResponseErrorDetails( response ) );
+ }
+
+ final String json = entityToString( response );
+ return objectMapper.readValue( json, typeRef );
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( post, response, client, metrics );
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ logger.debug( "Shutting down indy client HTTP manager" );
+ factory.shutdownNow();
+ }
+
+ /**
+ * clean just the cached file (storage of groups and remote repos)
+ */
+ public void deleteCache( final String path )
+ throws IndyClientException
+ {
+ delete( path + "?" + CHECK_CACHE_ONLY + "=true" );
+ }
+
+ public void delete( final String path )
+ throws IndyClientException
+ {
+ delete( path, HttpStatus.SC_NO_CONTENT, HttpStatus.SC_OK );
+ }
+
+ public void delete( final String path, final int... responseCodes )
+ throws IndyClientException
+ {
+ HttpDelete delete = newDelete( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( delete );
+
+ connect();
+
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( delete );
+
+ response = client.execute( delete, newContext() );
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error deleting: %s.\n%s", path,
+ new IndyResponseErrorDetails( response ) );
+ }
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( delete, response, client, metrics );
+ }
+ }
+
+ public void deleteWithChangelog( final String path, final String changelog )
+ throws IndyClientException
+ {
+ deleteWithChangelog( path, changelog, HttpStatus.SC_NO_CONTENT );
+ }
+
+ public void deleteWithChangelog( final String path, final String changelog, final int... responseCodes )
+ throws IndyClientException
+ {
+ HttpDelete delete = newDelete( buildUrl( baseUrl, path ) );
+ ClientMetrics metrics = metricManager.register( delete );
+
+ connect();
+
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ try
+ {
+ client = newClient();
+ addLoggingMDCToHeaders( delete );
+ delete.setHeader( "changelog", changelog );
+
+ response = client.execute( delete, newContext() );
+ final StatusLine sl = response.getStatusLine();
+ if ( !validResponseCode( sl.getStatusCode(), responseCodes ) )
+ {
+ metrics.registerErr( sl );
+ throw new IndyClientException( sl.getStatusCode(), "Error deleting: %s.\n%s", path,
+ new IndyResponseErrorDetails( response ) );
+ }
+ }
+ catch ( final IOException e )
+ {
+ metrics.registerErr( e );
+ throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() );
+ }
+ finally
+ {
+ metrics.registerEnd( response );
+ cleanupResources( delete, response, client, metrics );
+ }
+ }
+
+ public boolean exists( final String path )
+ throws IndyClientException
+ {
+ return exists( path, null, HttpStatus.SC_OK );
+ }
+
+ public boolean exists( final String path, Supplier