From 0f858a81097c133acd1ce4a1dfcbc47dfef10935 Mon Sep 17 00:00:00 2001 From: yma Date: Mon, 8 May 2023 11:29:38 +0800 Subject: [PATCH] Migrate all the necessary Indy Client Modules --- .gitignore | 41 + README.md | 4 +- pom.xml | 156 +++ .../org/commonjava/indy/client/core/Indy.java | 391 ++++++ .../client/core/IndyClientAuthenticator.java | 42 + .../indy/client/core/IndyClientException.java | 146 +++ .../indy/client/core/IndyClientHttp.java | 1141 +++++++++++++++++ .../indy/client/core/IndyClientModule.java | 67 + .../indy/client/core/IndyException.java | 128 ++ .../client/core/IndyResponseErrorDetails.java | 81 ++ .../indy/client/helper/HttpResources.java | 226 ++++ .../indy/client/helper/PathInfo.java | 102 ++ .../client/inject/ClientMetricConfig.java | 27 + .../indy/client/inject/ClientMetricSet.java | 30 + .../metric/ClientGoldenSignalsMetricSet.java | 47 + ...ClientGoldenSignalsSpanFieldsInjector.java | 59 + .../client/metric/ClientMetricConstants.java | 41 + .../client/metric/ClientMetricManager.java | 145 +++ .../indy/client/metric/ClientMetrics.java | 150 +++ .../client/metric/ClientMetricsProducer.java | 69 + .../metric/ClientTracerConfiguration.java | 205 +++ .../metric/ClientTrafficClassifier.java | 75 ++ .../indy/client/model/AbstractRepository.java | 101 ++ .../indy/client/model/AccessChannel.java | 38 + .../indy/client/model/ArtifactStore.java | 393 ++++++ .../model/ArtifactStoreValidateData.java | 116 ++ .../indy/client/model/BatchDeleteRequest.java | 118 ++ .../commonjava/indy/client/model/Group.java | 205 +++ .../indy/client/model/HostedRepository.java | 158 +++ .../indy/client/model/PathStyle.java | 25 + .../indy/client/model/RemoteRepository.java | 572 +++++++++ .../indy/client/model/StoreKey.java | 240 ++++ .../indy/client/model/StoreType.java | 98 ++ .../model/browse/ContentBrowseResult.java | 190 +++ .../client/model/folo/TrackedContentDTO.java | 96 ++ .../model/folo/TrackedContentEntryDTO.java | 252 ++++ .../client/model/folo/TrackingIdsDTO.java | 56 + .../indy/client/model/folo/TrackingKey.java | 111 ++ .../client/model/koji/KojiRepairRequest.java | 76 ++ .../client/model/koji/KojiRepairResult.java | 238 ++++ .../model/promote/AbstractPromoteRequest.java | 80 ++ .../model/promote/AbstractPromoteResult.java | 93 ++ .../client/model/promote/CallbackTarget.java | 93 ++ .../model/promote/PathsPromoteRequest.java | 169 +++ .../model/promote/PathsPromoteResult.java | 124 ++ .../client/model/promote/PromoteRequest.java | 40 + .../model/promote/ValidationResult.java | 118 ++ .../model/promote/ValidationRuleDTO.java | 116 ++ .../model/promote/ValidationRuleSet.java | 168 +++ .../model/store/SimpleBooleanResultDTO.java | 52 + .../client/model/store/StoreListingDTO.java | 80 ++ .../IndyContentBrowseClientModule.java | 52 + .../modules/IndyContentClientModule.java | 134 ++ .../modules/IndyFoloAdminClientModule.java | 141 ++ .../modules/IndyFoloContentClientModule.java | 119 ++ .../client/modules/IndyKojiClientModule.java | 48 + .../modules/IndyPromoteAdminClientModule.java | 111 ++ .../modules/IndyPromoteClientModule.java | 83 ++ .../client/modules/IndyRawHttpModule.java | 54 + .../IndySslValidationClientModule.java | 55 + .../modules/IndyStoreQueryClientModule.java | 249 ++++ .../modules/IndyStoresClientModule.java | 149 +++ .../client/util/PackageTypeConstants.java | 34 + .../commonjava/indy/client/util/UrlUtils.java | 195 +++ 64 files changed, 9012 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/org/commonjava/indy/client/core/Indy.java create mode 100644 src/main/java/org/commonjava/indy/client/core/IndyClientAuthenticator.java create mode 100644 src/main/java/org/commonjava/indy/client/core/IndyClientException.java create mode 100644 src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java create mode 100644 src/main/java/org/commonjava/indy/client/core/IndyClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/core/IndyException.java create mode 100644 src/main/java/org/commonjava/indy/client/core/IndyResponseErrorDetails.java create mode 100644 src/main/java/org/commonjava/indy/client/helper/HttpResources.java create mode 100644 src/main/java/org/commonjava/indy/client/helper/PathInfo.java create mode 100644 src/main/java/org/commonjava/indy/client/inject/ClientMetricConfig.java create mode 100644 src/main/java/org/commonjava/indy/client/inject/ClientMetricSet.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsMetricSet.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsSpanFieldsInjector.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientMetricConstants.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientMetricManager.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientMetrics.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientMetricsProducer.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientTracerConfiguration.java create mode 100644 src/main/java/org/commonjava/indy/client/metric/ClientTrafficClassifier.java create mode 100644 src/main/java/org/commonjava/indy/client/model/AbstractRepository.java create mode 100644 src/main/java/org/commonjava/indy/client/model/AccessChannel.java create mode 100644 src/main/java/org/commonjava/indy/client/model/ArtifactStore.java create mode 100644 src/main/java/org/commonjava/indy/client/model/ArtifactStoreValidateData.java create mode 100644 src/main/java/org/commonjava/indy/client/model/BatchDeleteRequest.java create mode 100644 src/main/java/org/commonjava/indy/client/model/Group.java create mode 100644 src/main/java/org/commonjava/indy/client/model/HostedRepository.java create mode 100644 src/main/java/org/commonjava/indy/client/model/PathStyle.java create mode 100644 src/main/java/org/commonjava/indy/client/model/RemoteRepository.java create mode 100644 src/main/java/org/commonjava/indy/client/model/StoreKey.java create mode 100644 src/main/java/org/commonjava/indy/client/model/StoreType.java create mode 100644 src/main/java/org/commonjava/indy/client/model/browse/ContentBrowseResult.java create mode 100644 src/main/java/org/commonjava/indy/client/model/folo/TrackedContentDTO.java create mode 100644 src/main/java/org/commonjava/indy/client/model/folo/TrackedContentEntryDTO.java create mode 100644 src/main/java/org/commonjava/indy/client/model/folo/TrackingIdsDTO.java create mode 100644 src/main/java/org/commonjava/indy/client/model/folo/TrackingKey.java create mode 100644 src/main/java/org/commonjava/indy/client/model/koji/KojiRepairRequest.java create mode 100644 src/main/java/org/commonjava/indy/client/model/koji/KojiRepairResult.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteRequest.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteResult.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/CallbackTarget.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteRequest.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteResult.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/PromoteRequest.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/ValidationResult.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleDTO.java create mode 100644 src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleSet.java create mode 100644 src/main/java/org/commonjava/indy/client/model/store/SimpleBooleanResultDTO.java create mode 100644 src/main/java/org/commonjava/indy/client/model/store/StoreListingDTO.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyContentBrowseClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyContentClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyFoloAdminClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyFoloContentClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyKojiClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyPromoteAdminClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyPromoteClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyRawHttpModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndySslValidationClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyStoreQueryClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/modules/IndyStoresClientModule.java create mode 100644 src/main/java/org/commonjava/indy/client/util/PackageTypeConstants.java create mode 100644 src/main/java/org/commonjava/indy/client/util/UrlUtils.java 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> querySupplier ) + throws IndyClientException + { + return exists( path, querySupplier, HttpStatus.SC_OK ); + } + + public boolean exists( final String path, final int... responseCodes ) + throws IndyClientException + { + return exists( path, null, responseCodes ); + } + + public boolean exists( final String path, Supplier> querySupplier, final int... responseCodes ) + throws IndyClientException + { + HttpHead request = newJsonHead( buildUrl( baseUrl, querySupplier, 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 ( validResponseCode( sl.getStatusCode(), responseCodes ) ) + { + return true; + } + else if ( sl.getStatusCode() == HttpStatus.SC_NOT_FOUND ) + { + return false; + } + + metrics.registerErr( sl ); + throw new IndyClientException( sl.getStatusCode(), "Error checking existence of: %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( request, response, client, metrics ); + } + } + + public void cleanup( final HttpRequest request, final HttpResponse response, final CloseableHttpClient client ) + { + cleanupResources( request, response, client, null ); + } + + public String toIndyUrl( final String... path ) + { + return buildUrl( baseUrl, path ); + } + + public String getBaseUrl() + { + return baseUrl; + } + + public CloseableHttpClient newClient() + throws IndyClientException + { + try + { + return factory.createClient( location, defaultHeaders ); + } + catch ( JHttpCException e ) + { + throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() ); + } + } + + public HttpClientContext newContext() + throws IndyClientException + { + try + { + return factory.createContext( location ); + } + catch ( JHttpCException e ) + { + throw new IndyClientException( "Indy request failed: %s", e, e.getMessage() ); + } + } + + public HttpGet newRawGet( final String url ) + { + return new HttpGet( url ); + } + + public HttpGet newJsonGet( final String url ) + { + final HttpGet req = new HttpGet( url ); + addJsonHeaders( req ); + return req; + } + + public HttpHead newJsonHead( final String url ) + { + final HttpHead req = new HttpHead( url ); + addJsonHeaders( req ); + return req; + } + + public HttpDelete newDelete( final String url ) + { + return new HttpDelete( url ); + } + + public HttpPut newJsonPut( final String url ) + { + final HttpPut req = new HttpPut( url ); + addJsonHeaders( req ); + return req; + } + + public HttpPut newRawPut( final String url ) + { + return new HttpPut( url ); + } + + public HttpPost newJsonPost( final String url ) + { + final HttpPost req = new HttpPost( url ); + addJsonHeaders( req ); + return req; + } + + public HttpPost newRawPost( final String url ) + { + final HttpPost req = new HttpPost( url ); + req.addHeader( "Content-Type", "application/json" ); + return req; + } + + protected void addJsonHeaders( final HttpUriRequest req ) + { + req.addHeader( "Accept", "application/json" ); + req.addHeader( "Content-Type", "application/json" ); + } + + public ObjectMapper getObjectMapper() + { + return objectMapper; + } + + public static SiteConfig defaultSiteConfig( String baseUrl ) + { + return new SiteConfigBuilder( "indy", baseUrl ).withRequestTimeoutSeconds( 30 ) + .withMaxConnections( IndyClientHttp.GLOBAL_MAX_CONNECTIONS ) + .build(); + } + + public void addDefaultHeader( String key, String value ) + { + if ( defaultHeaders == null ) + { + defaultHeaders = new ArrayList<>(); + } + defaultHeaders.add( new BasicHeader( key, value ) ); + } + + public String getDefaultHeader( String key ) + { + if ( defaultHeaders == null ) + { + return null; + } + for ( Header header : defaultHeaders ) + { + if ( header.getName().equals( key ) ) + { + return header.getValue(); + } + } + return null; + } + + private void addLoggingMDCToHeaders( HttpRequestBase request ) + { + Map context = MDC.getCopyOfContextMap(); + if ( context == null ) + { + return; + } + for ( Map.Entry mdcKeyHeaderKey : mdcCopyMappings.entrySet() ) + { + String mdcValue = context.get( mdcKeyHeaderKey.getKey() ); + if ( !StringUtils.isEmpty( mdcValue ) ) + { + request.addHeader( mdcKeyHeaderKey.getValue(), mdcValue ); + } + } + } +} diff --git a/src/main/java/org/commonjava/indy/client/core/IndyClientModule.java b/src/main/java/org/commonjava/indy/client/core/IndyClientModule.java new file mode 100644 index 0000000..641494e --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/core/IndyClientModule.java @@ -0,0 +1,67 @@ +/** + * 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 java.util.Collections; + +public abstract class IndyClientModule +{ + + protected IndyClientHttp http; + + protected Indy client; + + protected void setup( final Indy client, final IndyClientHttp http ) + { + this.client = client; + this.http = http; + } + + public Iterable getSerializerModules() + { + return Collections.emptySet(); + } + + protected Indy getClient() + { + return client; + } + + protected IndyClientHttp getHttp() + { + return http; + } + + protected ObjectMapper getObjectMapper() + { + return http.getObjectMapper(); + } + + @Override + public final int hashCode() + { + return 13 * getClass().hashCode(); + } + + @Override + public final boolean equals( final Object other ) + { + return other == this || getClass().equals( other.getClass() ); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/core/IndyException.java b/src/main/java/org/commonjava/indy/client/core/IndyException.java new file mode 100644 index 0000000..97d6c44 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/core/IndyException.java @@ -0,0 +1,128 @@ +/** + * 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 between the REST-resources layer and the next layer down (except for {@link DownloadManager}, which is normally two layers down thanks + * to binding controllers). Workflow exceptions are intended to carry with them some notion of what response to send to the user (even if it's + * the default: HTTP 500). + */ +public class IndyException + extends Exception +{ + private Object[] params; + + private transient String formattedMessage; + + public IndyException(final String message, final Object... params ) + { + super( message ); + this.params = params; + } + + public IndyException(final String message, final Throwable cause, final Object... params ) + { + super( message, cause ); + this.params = params; + } + + 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 + { + for(int i=0; i + * NOTE: This class stores the response entity {@link InputStream}, and is NOT threadsafe! + * + * @author jdcasey + * + */ +public class HttpResources + implements Closeable +{ + + private final AbstractExecutionAwareRequest request; + + private CloseableHttpResponse response; + + private final CloseableHttpClient client; + + private final ClientMetrics metrics; + + private InputStream responseEntityStream; + + public HttpResources( final AbstractExecutionAwareRequest request, CloseableHttpResponse response, + final CloseableHttpClient client, ClientMetrics metrics ) + { + this.request = request; + this.response = response; + this.client = client; + this.metrics = metrics; + } + + public void setResponse( final CloseableHttpResponse response ) + { + this.response = response; + } + + public static void cleanupResources( final HttpRequest request, final HttpResponse response, + final CloseableHttpClient client ) + { + cleanupResources( request, response, client, null ); + } + + public static void cleanupResources( final HttpRequest request, final HttpResponse response, + final CloseableHttpClient client, ClientMetrics metrics ) + { + final Logger logger = LoggerFactory.getLogger( HttpResources.class ); + logger.info( "CLEANING UP RESOURCES via: {}", Thread.currentThread() + .getStackTrace()[2] ); + + if ( response != null && response.getEntity() != null ) + { + EntityUtils.consumeQuietly( response.getEntity() ); + + if ( response instanceof CloseableHttpResponse ) + { + closeQuietly( (CloseableHttpResponse) response ); + } + } + + if ( request != null ) + { + if ( request instanceof HttpEntityEnclosingRequestBase ) + { + EntityUtils.consumeQuietly( ((HttpEntityEnclosingRequestBase) request ).getEntity() ); + } + if ( request instanceof AbstractExecutionAwareRequest ) + { + ( (AbstractExecutionAwareRequest) request ).reset(); + } + } + + if ( client != null ) + { + closeQuietly( client ); + } + + if ( metrics != null ) + { + closeQuietly( metrics ); + } + + logger.info( "DONE: CLEANING UP RESOURCES" ); + } + + public static String entityToString( final HttpResponse response ) + throws IOException + { + InputStream stream = null; + try + { + stream = response.getEntity() + .getContent(); + + return IOUtils.toString( stream ); + } + finally + { + closeQuietly( stream ); + } + } + + public HttpClient getClient() + { + return client; + } + + public HttpRequest getRequest() + { + return request; + } + + public HttpResponse getResponse() + { + return response; + } + + public InputStream getResponseStream() + throws IOException + { + return new HttpResourcesManagingInputStream( this, getResponseEntityContent() ); + } + + @Override + public void close() + throws IOException + { + Logger logger = LoggerFactory.getLogger( getClass() ); + logger.debug( "Closing resources for: {}", request == null ? "UNKNOWN" : request.getRequestLine().getUri() ); + if ( responseEntityStream != null ) + { + logger.debug( "Closing response entity stream" ); + // do this quietly, since it's probable that the wrapper HttpResourcesManagingInputStream will have closed it implicitly before this is called. + IOUtils.closeQuietly( responseEntityStream ); + } + + cleanupResources( request, response, client, metrics ); + } + + public int getStatusCode() + { + return response.getStatusLine() + .getStatusCode(); + } + + public StatusLine getStatusLine() + { + return response.getStatusLine(); + } + + public InputStream getResponseEntityContent() + throws IOException + { + if ( responseEntityStream == null ) + { + responseEntityStream = response.getEntity() + .getContent(); + } + + return responseEntityStream; + } + + public InputStream wrapRequestStream( final InputStream stream ) + { + return new HttpResourcesManagingInputStream( this, stream ); + } + + private static final class HttpResourcesManagingInputStream + extends FilterInputStream + { + + private final HttpResources resources; + + HttpResourcesManagingInputStream( final HttpResources httpResources, final InputStream stream ) + { + super( stream ); + this.resources = httpResources; + } + + @Override + public void close() + throws IOException + { + super.close(); + IOUtils.closeQuietly( resources ); + } + + } +} diff --git a/src/main/java/org/commonjava/indy/client/helper/PathInfo.java b/src/main/java/org/commonjava/indy/client/helper/PathInfo.java new file mode 100644 index 0000000..9f19957 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/helper/PathInfo.java @@ -0,0 +1,102 @@ +/** + * 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.helper; + + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +public class PathInfo +{ + private final boolean exists; + + private final String contentType; + + private final long contentLength; + + private final Date lastModified; + + private static final String DATE_HEADER_FMT = "EEE, dd MMM yyyy HH:mm:ss"; + + public PathInfo( final Map headers ) + { + if ( headers == null ) + { + exists = false; + contentType = null; + contentLength = -1; + lastModified = null; + } + else + { + exists = true; + + contentType = headers.get( "content-type" ); + + final String cl = headers.get("content-length"); + contentLength = cl == null ? -1 : Long.parseLong( cl ); + + final String lm = headers.get( "last-modified" ); + + Date lastModified; + try + { + lastModified = lm == null ? null : parseDateHeader( lm ); + } + catch ( final ParseException e ) + { + lastModified = null; + } + + this.lastModified = lastModified; + } + } + + public boolean exists() + { + return exists; + } + + public String getContentType() + { + return contentType; + } + + public long getContentLength() + { + return contentLength; + } + + public Date getLastModified() + { + return lastModified; + } + + @Override + public String toString() + { + return String.format( "PathInfo [exists=%s, contentType=%s, contentLength=%s, lastModified=%s]", exists, + contentType, contentLength, lastModified ); + } + + private Date parseDateHeader( final String date ) + throws ParseException + { + return new SimpleDateFormat( DATE_HEADER_FMT ).parse( date ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/inject/ClientMetricConfig.java b/src/main/java/org/commonjava/indy/client/inject/ClientMetricConfig.java new file mode 100644 index 0000000..8a01fd8 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/inject/ClientMetricConfig.java @@ -0,0 +1,27 @@ +/** + * 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.inject; + +import javax.inject.Qualifier; +import java.lang.annotation.*; + +@Qualifier +@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD } ) +@Retention( RetentionPolicy.RUNTIME ) +@Documented +public @interface ClientMetricConfig { + +} diff --git a/src/main/java/org/commonjava/indy/client/inject/ClientMetricSet.java b/src/main/java/org/commonjava/indy/client/inject/ClientMetricSet.java new file mode 100644 index 0000000..39fb392 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/inject/ClientMetricSet.java @@ -0,0 +1,30 @@ +/** + * 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.inject; + +import javax.inject.Qualifier; +import java.lang.annotation.*; + +/** + * Qualifier used to supply the client metric set. + */ +@Qualifier +@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD } ) +@Retention( RetentionPolicy.RUNTIME ) +@Documented +public @interface ClientMetricSet { + +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsMetricSet.java b/src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsMetricSet.java new file mode 100644 index 0000000..53cbcc6 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsMetricSet.java @@ -0,0 +1,47 @@ +/** + * 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.metric; + +import org.commonjava.indy.client.inject.ClientMetricSet; +import org.commonjava.o11yphant.metrics.sli.GoldenSignalsFunctionMetrics; +import org.commonjava.o11yphant.metrics.sli.GoldenSignalsMetricSet; +import java.util.Arrays; +import java.util.Collection; + +import static org.commonjava.indy.client.metric.ClientMetricConstants.CLIENT_FUNCTIONS; + +@ClientMetricSet +public class ClientGoldenSignalsMetricSet + extends GoldenSignalsMetricSet { + + public ClientGoldenSignalsMetricSet() { + super(); + } + + @Override + protected Collection getFunctions() { + return Arrays.asList( CLIENT_FUNCTIONS ); + } + + public void clear() + { + getFunctionMetrics().clear(); + getFunctions().forEach( function -> { + getFunctionMetrics().put( function, new GoldenSignalsFunctionMetrics( function ) ); + } ); + } + +} \ No newline at end of file diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsSpanFieldsInjector.java b/src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsSpanFieldsInjector.java new file mode 100644 index 0000000..efe5420 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientGoldenSignalsSpanFieldsInjector.java @@ -0,0 +1,59 @@ +/** + * 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.metric; + +import org.commonjava.o11yphant.metrics.api.Gauge; +import org.commonjava.o11yphant.metrics.api.Meter; +import org.commonjava.o11yphant.metrics.api.Metric; +import org.commonjava.o11yphant.metrics.api.Timer; +import org.commonjava.o11yphant.trace.spi.SpanFieldsInjector; +import org.commonjava.o11yphant.trace.spi.adapter.SpanAdapter; +import java.util.Map; + +public class ClientGoldenSignalsSpanFieldsInjector + implements SpanFieldsInjector +{ + private final ClientGoldenSignalsMetricSet goldenSignalsMetricSet; + + public ClientGoldenSignalsSpanFieldsInjector( ClientGoldenSignalsMetricSet goldenSignalsMetricSet ) + { + this.goldenSignalsMetricSet = goldenSignalsMetricSet; + } + + @Override + public void decorateSpanAtClose( SpanAdapter span ) + { + final Map metrics = goldenSignalsMetricSet.getMetrics(); + metrics.forEach( ( k, v ) -> { + Object value = null; + if ( v instanceof Gauge ) + { + value = ( (Gauge) v ).getValue(); + span.addField( "golden." + k, value ); + } + else if ( v instanceof Timer ) + { + value = ( (Timer) v ).getSnapshot().get95thPercentile(); + span.addField( "golden." + k + ".p95", value ); + } + else if ( v instanceof Meter ) + { + value = ( (Meter) v ).getOneMinuteRate(); + span.addField( "golden." + k + ".m1", value ); + } + } ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientMetricConstants.java b/src/main/java/org/commonjava/indy/client/metric/ClientMetricConstants.java new file mode 100644 index 0000000..bf9ca49 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientMetricConstants.java @@ -0,0 +1,41 @@ +/** + * 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.metric; + +public class ClientMetricConstants { + + public static final String CLIENT_FOLO_ADMIN = "client.folo.admin"; + + public static final String CLIENT_FOLO_CONTENT = "client.folo.content"; + + public static final String CLIENT_REPO_MGMT = "client.repo.mgmt"; + + public static final String CLIENT_CONTENT = "client.content"; + + public static final String CLIENT_PROMOTE = "client.promote"; + + public final static String HEADER_CLIENT_API = "Indy-Client-API"; + + public final static String HEADER_CLIENT_TRACE_ID = "Indy-Client-Trace-Id"; + + public final static String HEADER_CLIENT_SPAN_ID = "Indy-Client-Span-Id"; + + public static final String[] CLIENT_FUNCTIONS = + { CLIENT_FOLO_ADMIN, CLIENT_FOLO_CONTENT, CLIENT_REPO_MGMT, CLIENT_CONTENT, CLIENT_PROMOTE }; + + private ClientMetricConstants() { + } +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientMetricManager.java b/src/main/java/org/commonjava/indy/client/metric/ClientMetricManager.java new file mode 100644 index 0000000..ce9d606 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientMetricManager.java @@ -0,0 +1,145 @@ +/** + * 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.metric; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.HttpUriRequest; +import org.commonjava.indy.client.inject.ClientMetricSet; +import org.commonjava.o11yphant.honeycomb.HoneycombConfiguration; +import org.commonjava.o11yphant.honeycomb.HoneycombTracePlugin; +import org.commonjava.o11yphant.otel.OtelConfiguration; +import org.commonjava.o11yphant.otel.OtelTracePlugin; +import org.commonjava.o11yphant.trace.SpanFieldsDecorator; +import org.commonjava.o11yphant.trace.TraceManager; +import org.commonjava.o11yphant.trace.TracerConfiguration; +import org.commonjava.o11yphant.trace.spi.O11yphantTracePlugin; +import org.commonjava.util.jhttpc.model.SiteConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +@SuppressWarnings( { "rawtypes", "unused" } ) +public class ClientMetricManager +{ + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + private ClientTracerConfiguration configuration; + + private TraceManager traceManager; + + private final ClientTrafficClassifier classifier = new ClientTrafficClassifier(); + + @ClientMetricSet + private final ClientGoldenSignalsMetricSet metricSet = new ClientGoldenSignalsMetricSet(); + + public ClientMetricManager() + { + } + + public ClientMetricManager( SiteConfig siteConfig ) + { + this.configuration = buildConfig( siteConfig ); + buildTraceManager(); + } + + public ClientMetricManager( TracerConfiguration existedTraceConfig ) + { + this.configuration = new ClientTracerConfiguration(); + this.configuration.setEnabled( existedTraceConfig.isEnabled() ); + this.configuration.setConsoleTransport( existedTraceConfig.isConsoleTransport() ); + this.configuration.setBaseSampleRate( existedTraceConfig.getBaseSampleRate() ); + this.configuration.setCpNames( existedTraceConfig.getCPNames() ); + this.configuration.setFields( existedTraceConfig.getFieldSet() ); + this.configuration.setEnvironmentMappings( existedTraceConfig.getEnvironmentMappings() ); + if ( existedTraceConfig instanceof OtelConfiguration ) + { + OtelConfiguration existedOtelConfig = (OtelConfiguration) existedTraceConfig; + this.configuration.setGrpcUri( existedOtelConfig.getGrpcEndpointUri() ); + this.configuration.setGrpcHeaders( existedOtelConfig.getGrpcHeaders() ); + this.configuration.setGrpcResources( existedOtelConfig.getResources() ); + } + else if ( existedTraceConfig instanceof HoneycombConfiguration ) + { + HoneycombConfiguration existedHoneyConfig = (HoneycombConfiguration) existedTraceConfig; + this.configuration.setWriteKey( existedHoneyConfig.getWriteKey() ); + this.configuration.setDataset( existedHoneyConfig.getDataset() ); + } + buildTraceManager(); + } + private void buildTraceManager(){ + if ( this.configuration.isEnabled() ) + { + O11yphantTracePlugin plugin = + new HoneycombTracePlugin( configuration, configuration, Optional.of( classifier ) ); + if ( StringUtils.isNotBlank( configuration.getGrpcEndpointUri() ) ) + { + plugin = new OtelTracePlugin( configuration, configuration ); + } + this.traceManager = new TraceManager<>( plugin, new SpanFieldsDecorator( + Collections.singletonList( new ClientGoldenSignalsSpanFieldsInjector( metricSet ) ) ), + configuration ); + } + } + + public ClientMetrics register( HttpUriRequest request ) + { + logger.debug( "Client honey register: {}", request.getURI().getPath() ); + List functions = classifier.calculateClassifiers( request ); + + return new ClientMetrics( configuration.isEnabled(), request, functions, metricSet ); + } + + private ClientTracerConfiguration buildConfig( SiteConfig siteConfig ) + { + ClientTracerConfiguration config = new ClientTracerConfiguration(); + config.setEnabled( siteConfig.isMetricEnabled() ); + config.setBaseSampleRate( siteConfig.getBaseSampleRate() ); + return config; + } + + private String getEndpointName( String method, String pathInfo ) + { + StringBuilder sb = new StringBuilder( method + "_" ); + String[] toks = pathInfo.split( "/" ); + for ( String s : toks ) + { + if ( isBlank( s ) || "api".equals( s ) ) + { + continue; + } + sb.append( s ); + if ( "admin".equals( s ) ) + { + sb.append( "_" ); + } + else + { + break; + } + } + return sb.toString(); + } + + public Optional getTraceManager() + { + return traceManager == null ? Optional.empty() : Optional.of( traceManager ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientMetrics.java b/src/main/java/org/commonjava/indy/client/metric/ClientMetrics.java new file mode 100644 index 0000000..f4c2ff3 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientMetrics.java @@ -0,0 +1,150 @@ +/** + * 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.metric; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.commonjava.o11yphant.metrics.RequestContextHelper; +import org.commonjava.o11yphant.metrics.sli.GoldenSignalsFunctionMetrics; +import org.slf4j.Logger; +import java.io.Closeable; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import static org.commonjava.o11yphant.metrics.MetricsConstants.NANOS_PER_MILLISECOND; +import static org.commonjava.o11yphant.metrics.RequestContextConstants.*; +import static org.commonjava.o11yphant.trace.TraceManager.addFieldToActiveSpan; +import static org.slf4j.LoggerFactory.getLogger; + +public class ClientMetrics + extends ClientMetricManager implements Closeable +{ + private static final String ERROR = "request-error"; + + private final boolean enabled; + + private final HttpUriRequest request; + + private final Collection functions; + + private final ClientGoldenSignalsMetricSet metricSet; + + private final Logger logger = getLogger( getClass().getName() ); + + private final long start; + + private HttpResponse response; + + private long end; + + public ClientMetrics( boolean enabled, HttpUriRequest request, Collection functions, + ClientGoldenSignalsMetricSet metricSet ) + { + this.enabled = enabled; + this.request = request; + this.functions = functions; + this.metricSet = metricSet; + this.start = System.nanoTime(); + + if ( enabled ) + { + logger.debug( "Client trace starting: {}", request.getURI().getPath() ); + functions.forEach( function -> metricSet.function( function ).ifPresent( GoldenSignalsFunctionMetrics::started ) ); + + Set classifierTokens = new LinkedHashSet<>(); + functions.forEach( function -> { + String[] parts = function.split( "\\." ); + for ( int i = 0; i < parts.length - 1; i++ ) + { + classifierTokens.add( parts[i] ); + } + } ); + + String classification = StringUtils.join( classifierTokens, "," ); + + RequestContextHelper.setContext( TRAFFIC_TYPE, classification ); + addFieldToActiveSpan( TRAFFIC_TYPE, classification ); + } + } + + public void registerErr( Object error ) { + if ( !enabled ) + { + return; + } + + logger.debug( "Client trace registerErr: {}", request.getURI().getPath() ); + if ( error instanceof Throwable ) + { + StringBuilder sb = new StringBuilder(); + sb.append( error.getClass().getSimpleName() ); + sb.append( ": " ); + sb.append( ( (Throwable) error ).getMessage() ); + addFieldToActiveSpan( ERROR, sb ); + } + else + { + addFieldToActiveSpan( ERROR, error ); + } + functions.forEach( function -> metricSet.function( function ) + .ifPresent( GoldenSignalsFunctionMetrics::error ) ); + } + + public void registerEnd( HttpResponse response ) + { + if ( !enabled || response == null ) + { + return; + } + + this.response = response; + + logger.debug( "Client trace registerEnd: {}", request.getURI().getPath() ); + boolean error = ( response != null && response.getStatusLine() != null ) && ( response.getStatusLine().getStatusCode() > 499 ); + + functions.forEach( function -> metricSet.function( function ).ifPresent( functionMetrics -> { + functionMetrics.latency( end - start ).call(); + if ( error ) { + functionMetrics.error(); + } + } ) ); + } + + @Override + public void close() + { + if ( !enabled ) + { + return; + } + + logger.trace( "Client trace closing: {}", request.getURI().getPath() ); + + this.end = RequestContextHelper.getRequestEndNanos() - RequestContextHelper.getRawIoWriteNanos(); + RequestContextHelper.setContext( REQUEST_LATENCY_NS, String.valueOf( end - start ) ); + RequestContextHelper.setContext( REQUEST_LATENCY_MILLIS, ( end - start ) / NANOS_PER_MILLISECOND ); + + if ( metricSet.getFunctionMetrics().isEmpty() ) { + logger.trace( "Client trace metricSet is empty: {}", request.getURI().getPath() ); + return; + } + String pathInfo = request.getURI().getPath(); + + addFieldToActiveSpan( "path_info", pathInfo ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientMetricsProducer.java b/src/main/java/org/commonjava/indy/client/metric/ClientMetricsProducer.java new file mode 100644 index 0000000..5b305fa --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientMetricsProducer.java @@ -0,0 +1,69 @@ +/** + * 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.metric; + +import org.commonjava.cdi.util.weft.config.DefaultWeftConfig; +import org.commonjava.cdi.util.weft.config.WeftConfig; +import org.commonjava.o11yphant.metrics.TrafficClassifier; +import org.commonjava.o11yphant.metrics.conf.DefaultMetricsConfig; +import org.commonjava.o11yphant.metrics.conf.MetricsConfig; +import org.commonjava.o11yphant.metrics.sli.GoldenSignalsMetricSet; +import org.commonjava.o11yphant.metrics.system.StoragePathProvider; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Produces; + +/** + * This producer is used to provide the missing CDI deps for indy client metrics sets. Now all + * produces provided alternative ones, because it will break indy metrics providers. In future the + * indy-client libs will be extracted to a single lib, so then we will set these providers as default. + */ +public class ClientMetricsProducer +{ + @Produces + @Alternative + public TrafficClassifier getClientTrafficClassifier() + { + return new ClientTrafficClassifier(); + } + + @Produces + @Alternative + public GoldenSignalsMetricSet getClientMetricSet() + { + return new ClientGoldenSignalsMetricSet(); + } + + @Produces + @Alternative + public MetricsConfig getMetricsConfig() + { + return new DefaultMetricsConfig(); + } + + @Produces + @Alternative + public WeftConfig getWeftConfig() + { + return new DefaultWeftConfig(); + } + + @Produces + @Alternative + public StoragePathProvider getStoragePathProvider() + { + return () -> null; + } +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientTracerConfiguration.java b/src/main/java/org/commonjava/indy/client/metric/ClientTracerConfiguration.java new file mode 100644 index 0000000..6ff87bc --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientTracerConfiguration.java @@ -0,0 +1,205 @@ +/** + * 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.metric; + +import org.apache.commons.lang3.StringUtils; +import org.commonjava.indy.client.inject.ClientMetricConfig; +import org.commonjava.o11yphant.honeycomb.HoneycombConfiguration; +import org.commonjava.o11yphant.otel.OtelConfiguration; +import org.commonjava.o11yphant.trace.TracerConfiguration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings( "unused" ) +@ClientMetricConfig +public class ClientTracerConfiguration + implements TracerConfiguration, OtelConfiguration, HoneycombConfiguration +{ + private static final Integer DEFAULT_BASE_SAMPLE_RATE = 100; + + private static final String DEFAULT_INDY_CLIENT_SERVICE_NAME = "indy-client"; + + private boolean enabled; + + private String serviceName; + + private boolean consoleTransport; + + private String writeKey; + + private String dataset; + + private Integer baseSampleRate; + + private final Map spanRates = new HashMap<>(); + + private Set fields; + + private String environmentMappings; + + private String cpNames; + + private String grpcUri; + + private Map grpcHeaders = new HashMap<>(); + + private Map grpcResources = new HashMap<>(); + + @Override + public Map getSpanRates() + { + return spanRates; + } + + @Override + public boolean isEnabled() + { + return enabled; + } + + @Override + public boolean isConsoleTransport() + { + return consoleTransport; + } + + @Override + public String getWriteKey() + { + return writeKey; + } + + @Override + public String getDataset() + { + return dataset; + } + + @Override + public Integer getBaseSampleRate() + { + return baseSampleRate == null ? DEFAULT_BASE_SAMPLE_RATE : baseSampleRate; + } + + @Override + public Set getFieldSet() + { + return fields == null ? DEFAULT_FIELDS : fields; + } + + @Override + public String getEnvironmentMappings() + { + return environmentMappings; + } + + @Override + public String getCPNames() + { + return cpNames; + } + + @Override + public Map getGrpcHeaders() + { + return grpcHeaders; + } + + @Override + public Map getResources() + { + return grpcResources; + } + + @Override + public String getServiceName() + { + return StringUtils.isBlank( serviceName ) ? DEFAULT_INDY_CLIENT_SERVICE_NAME : serviceName; + } + + @Override + public String getNodeId() + { + return null; + } + + @Override + public String getGrpcEndpointUri() + { + return grpcUri == null ? DEFAULT_GRPC_URI : grpcUri; + } + + public void setDataset( String dataset ) + { + this.dataset = dataset; + } + + public void setWriteKey( String writeKey ) + { + this.writeKey = writeKey; + } + + public void setConsoleTransport( boolean consoleTransport ) + { + this.consoleTransport = consoleTransport; + } + + public void setEnabled( boolean enabled ) + { + this.enabled = enabled; + } + + public void setBaseSampleRate( Integer baseSampleRate ) + { + this.baseSampleRate = baseSampleRate; + } + + public void setFields( Set fields ) + { + this.fields = fields; + } + + public void setEnvironmentMappings( String environmentMappings ) + { + this.environmentMappings = environmentMappings; + } + + public void setCpNames( String cpNames ) + { + this.cpNames = cpNames; + } + + public void setGrpcUri( String grpcUri ) + { + this.grpcUri = grpcUri; + } + + public void setServiceName( String serviceName ) + { + this.serviceName = serviceName; + } + + public void setGrpcHeaders( Map grpcHeaders ) + { + this.grpcHeaders = grpcHeaders; + } + + public void setGrpcResources( Map grpcResources ) + { + this.grpcResources = grpcResources; + } +} diff --git a/src/main/java/org/commonjava/indy/client/metric/ClientTrafficClassifier.java b/src/main/java/org/commonjava/indy/client/metric/ClientTrafficClassifier.java new file mode 100644 index 0000000..fb6c8fb --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/metric/ClientTrafficClassifier.java @@ -0,0 +1,75 @@ +/** + * 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.metric; + +import org.apache.http.Header; +import org.apache.http.client.methods.HttpUriRequest; +import org.commonjava.o11yphant.metrics.TrafficClassifier; +import javax.enterprise.inject.Alternative; +import java.util.*; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.StringUtils.join; +import static org.commonjava.indy.client.metric.ClientMetricConstants.*; + + +@Alternative +public class ClientTrafficClassifier + implements TrafficClassifier +{ + + public static final Set FOLO_RECORD_ENDPOINTS = new HashSet<>( asList( "record", "report" ) ); + + @Override + public List classifyFunctions( String restPath, String method, Map headers ) { + return calculateCachedFunctionClassifiers( restPath, method, headers ); + } + + public List calculateClassifiers( HttpUriRequest request ) { + Map headers = new HashMap<>(); + for ( Header header : request.getAllHeaders() ) + { + headers.put( header.getName(), header.getValue() ); + } + return calculateCachedFunctionClassifiers( request.getURI().getPath(), request.getMethod(), headers ); + } + + protected List calculateCachedFunctionClassifiers( String restPath, String method, Map headers ) { + List result = new ArrayList<>(); + String[] pathParts = restPath.split( "/" ); + + if ( pathParts.length >= 2 ) { + String[] classifierParts = new String[ pathParts.length - 1 ]; + System.arraycopy( pathParts, 1, classifierParts, 0, classifierParts.length ); + + String restPrefix = join( classifierParts, '/' ); + if ( restPrefix.startsWith( "folo/admin/" ) && FOLO_RECORD_ENDPOINTS.contains( classifierParts[ 3 ] ) ) { + result = singletonList( CLIENT_FOLO_ADMIN ); + } else if ( restPrefix.startsWith( "folo/track/" ) && classifierParts.length > 6 ) { + result = singletonList( CLIENT_FOLO_CONTENT ); + } else if ( "admin".equals( classifierParts[ 0 ] ) && "stores".equals( classifierParts[ 1 ] ) + && classifierParts.length > 2 ) { + result = singletonList( CLIENT_REPO_MGMT ); + } else if ( ( "content".equals( classifierParts[ 0 ] ) && classifierParts.length > 5 ) ) { + result = singletonList( CLIENT_CONTENT ); + } else if ( restPrefix.startsWith( "promotion/paths/" ) ) { + result = singletonList( CLIENT_PROMOTE ); + } + } + return result; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/AbstractRepository.java b/src/main/java/org/commonjava/indy/client/model/AbstractRepository.java new file mode 100644 index 0000000..1e5209d --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/AbstractRepository.java @@ -0,0 +1,101 @@ +/** + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public abstract class AbstractRepository + extends ArtifactStore + implements Externalizable +{ + private static final int ABSTRACT_REPOSITORY_VERSION = 1; + + @JsonProperty( "allow_snapshots" ) + private boolean allowSnapshots = false; + + @JsonProperty( "allow_releases" ) + private boolean allowReleases = true; + + + public AbstractRepository() + { + super(); + } + + protected AbstractRepository( final String packageType, final StoreType type, final String name ) + { + super( packageType, type, name ); + } + + public boolean isAllowSnapshots() + { + return allowSnapshots; + } + + public void setAllowSnapshots( final boolean allowSnapshots ) + { + this.allowSnapshots = allowSnapshots; + } + + public boolean isAllowReleases() + { + return allowReleases; + } + + public void setAllowReleases( final boolean allowReleases ) + { + this.allowReleases = allowReleases; + } + + protected void copyRestrictions( AbstractRepository repo ) + { + repo.setAllowReleases( isAllowReleases() ); + repo.setAllowSnapshots( isAllowSnapshots() ); + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + super.writeExternal( out ); + + out.writeInt( ABSTRACT_REPOSITORY_VERSION ); + out.writeBoolean( allowReleases ); + out.writeBoolean( allowSnapshots ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + super.readExternal( in ); + + int abstractRepositoryVersion = in.readInt(); + if ( abstractRepositoryVersion > ABSTRACT_REPOSITORY_VERSION ) + { + throw new IOException( "Cannot deserialize. AbstractRepository version in data stream is: " + abstractRepositoryVersion + + " but this class can only deserialize up to version: " + ABSTRACT_REPOSITORY_VERSION ); + } + + this.allowReleases = in.readBoolean(); + this.allowSnapshots = in.readBoolean(); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/AccessChannel.java b/src/main/java/org/commonjava/indy/client/model/AccessChannel.java new file mode 100644 index 0000000..b07152a --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/AccessChannel.java @@ -0,0 +1,38 @@ +/** + * 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.model; + + +/** + * Enumeration to distinguish between different access channels to stores. + * + * @author pkocandr + */ +public enum AccessChannel +{ + + /** Used when the store is accessed via httprox addon. */ + GENERIC_PROXY, + + /** Used to signify content coming from normal repositories and groups. */ + NATIVE, + + /** Used when the store is accessed via regular Maven repo. + * NOTE: This has been changed to {@link #NATIVE} in our tracking code. It is included for historical purposes. */ + @Deprecated + MAVEN_REPO; + +} diff --git a/src/main/java/org/commonjava/indy/client/model/ArtifactStore.java b/src/main/java/org/commonjava/indy/client/model/ArtifactStore.java new file mode 100644 index 0000000..e0c65de --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/ArtifactStore.java @@ -0,0 +1,393 @@ +/** + * 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.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import static org.commonjava.indy.client.model.PathStyle.plain; + +@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = ArtifactStore.TYPE_ATTR ) +@JsonSubTypes( { @Type( name = "remote", value = RemoteRepository.class ), + @Type( name = "hosted", value = HostedRepository.class ), @Type( name = "group", value = Group.class ) } ) +@ApiModel( description = "Definition of a content store on Indy, whether it proxies content from a remote server, hosts artifacts on this system, or groups other content stores.", discriminator = "type", subTypes = { + HostedRepository.class, Group.class, RemoteRepository.class } ) +public abstract class ArtifactStore + implements Externalizable +{ + + private static final int ARTIFACT_STORE_VERSION = 1; + + public static final String PKG_TYPE_ATTR = "packageType"; + + public static final String TYPE_ATTR = "type"; + + public static final String KEY_ATTR = "key"; + + public static final String METADATA_CHANGELOG = "changelog"; + + public static final String METADATA_ORIGIN = "origin"; + + public static final String TRACKING_ID = "trackingId"; + + @ApiModelProperty( required = true, dataType = "string", value = "Serialized store key, of the form: '[hosted|group|remote]:name'" ) + private StoreKey key; + + private String description; + + private transient Map transientMetadata; + + private Map metadata; + + private boolean disabled; + + @ApiModelProperty( required = false, dataType = "int", value = "Integer time in seconds which is used for repo automatically re-enable when set disable by errors, positive value means time in seconds, -1 means never disable, empty or 0 means use default timeout." ) + @JsonProperty( "disable_timeout" ) + private int disableTimeout; + + @JsonProperty( "path_style" ) + private PathStyle pathStyle; + + @JsonProperty( "path_mask_patterns" ) + private Set pathMaskPatterns; + + // If true, the content in the repo will be authoritatively indexed. Transfers will be treated as missing if it is not in content-index. + @JsonProperty("authoritative_index") + private Boolean authoritativeIndex; + + @JsonProperty("create_time") + private String createTime; + + @JsonIgnore + private Boolean rescanInProgress = false; + + public ArtifactStore() + { + initRepoTime(); + } + + protected ArtifactStore( final String packageType, final StoreType type, final String name ) + { + this.key = StoreKey.dedupe( new StoreKey( packageType, type, name ) ); + initRepoTime(); + } + + public String getName() + { + return key.getName(); + } + + public StoreType getType() + { + return key.getType(); + } + + public String getPackageType() + { + return key.getPackageType(); + } + + public StoreKey getKey() + { + return key; + } + + public abstract ArtifactStore copyOf(); + + public abstract ArtifactStore copyOf( String packageType, String name ); + + @Override + public final int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( key == null ) ? 19 : key.hashCode() ); + return result; + } + + @Override + public final boolean equals( final Object obj ) + { + if ( this == obj ) + { + return true; + } + if ( getClass() != obj.getClass() ) + { + return false; + } + final ArtifactStore other = (ArtifactStore) obj; + if ( key == null ) + { + if ( other.key != null ) + { + return false; + } + } + else if ( !key.equals( other.key ) ) + { + return false; + } + return true; + } + + public boolean isDisabled() + { + return disabled; + } + + public void setDisabled( boolean disabled ) + { + this.disabled = disabled; + } + + public int getDisableTimeout() + { + return disableTimeout; + } + + public void setDisableTimeout( int disableTimeout ) + { + this.disableTimeout = disableTimeout; + } + + public void setMetadata( final Map metadata ) + { + this.metadata = metadata; + } + + public Map getMetadata() + { + return metadata; + } + + public synchronized String setMetadata( final String key, final String value ) + { + if ( key == null || value == null ) + { + return null; + } + + if ( metadata == null ) + { + metadata = new HashMap<>(); + } + + return metadata.put( key, value ); + } + + public String getMetadata( final String key ) + { + return metadata == null ? null : metadata.get( key ); + } + + public synchronized Object removeTransientMetadata( final String key ) + { + if ( transientMetadata == null || key == null ) + { + return null; + } + + return transientMetadata.remove( key ); + } + + public synchronized Object setTransientMetadata( final String key, final Object value ) + { + if ( key == null || value == null ) + { + return null; + } + + if ( transientMetadata == null ) + { + transientMetadata = new HashMap<>(); + } + + return transientMetadata.put( key, value ); + } + + public Object getTransientMetadata( final String key ) + { + return transientMetadata == null ? null : transientMetadata.get( key ); + } + + protected void copyBase( ArtifactStore store ) + { + store.setRescanInProgress( isRescanInProgress() ); + store.setDescription( getDescription() ); + store.setDisabled( isDisabled() ); + store.setMetadata( getMetadata() ); + store.setTransientMetadata( getTransientMetadata() ); + store.setPathStyle( getPathStyle() ); + store.setDisableTimeout( getDisableTimeout() ); + store.setPathMaskPatterns( getPathMaskPatterns() ); + store.setAuthoritativeIndex( isAuthoritativeIndex() ); + } + + protected void setTransientMetadata( Map transientMetadata ) + { + this.transientMetadata = transientMetadata; + } + + protected Map getTransientMetadata() + { + return transientMetadata; + } + + @Override + public String toString() + { + return String.format( "%s [key=%s]", getClass().getSimpleName(), key ); + } + + public String getDescription() + { + return description; + } + + public void setDescription( final String description ) + { + this.description = description; + } + + public PathStyle getPathStyle() + { + return pathStyle == null ? plain : pathStyle; + } + + public void setPathStyle( PathStyle pathStyle ) + { + this.pathStyle = pathStyle; + } + + public Set getPathMaskPatterns() + { + return pathMaskPatterns; + } + + public void setPathMaskPatterns( Set pathMaskPatterns ) + { + this.pathMaskPatterns = pathMaskPatterns; + } + + public boolean isAuthoritativeIndex() + { + return authoritativeIndex == null ? Boolean.FALSE : authoritativeIndex; + } + + public void setAuthoritativeIndex( boolean authoritativeIndex ) + { + this.authoritativeIndex = authoritativeIndex; + } + + public Boolean isRescanInProgress() + { + return rescanInProgress; + } + + public void setRescanInProgress( Boolean rescanInProgress ) + { + this.rescanInProgress = rescanInProgress; + } + + public String getCreateTime() + { + return createTime; + } + + private void initRepoTime() + { + final SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss ZZZ" ); + format.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); + this.createTime = format.format( new Date( System.currentTimeMillis() ) ); + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + out.writeInt( ARTIFACT_STORE_VERSION ); + out.writeObject( key ); + + out.writeObject( description ); + out.writeObject( metadata ); + out.writeBoolean( disabled ); + out.writeInt( disableTimeout ); + + if ( pathStyle == null ) + { + out.writeObject( null ); + } + else + { + out.writeObject( pathStyle.name() ); + } + + out.writeObject( pathMaskPatterns ); + out.writeObject( authoritativeIndex ); + out.writeObject( createTime ); + out.writeObject( rescanInProgress ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + int artifactStoreVersion = in.readInt(); + if ( artifactStoreVersion > ARTIFACT_STORE_VERSION ) + { + throw new IOException( "Cannot deserialize. ArtifactStore version in data stream is: " + artifactStoreVersion + + " but this class can only deserialize up to version: " + ARTIFACT_STORE_VERSION ); + } + + this.key = (StoreKey) in.readObject(); + + this.description = (String) in.readObject(); + this.metadata = (Map) in.readObject(); + this.disabled = in.readBoolean(); + this.disableTimeout = in.readInt(); + + Object rawPathStyle = in.readObject(); + if ( rawPathStyle == null ) + { + this.pathStyle = null; + } + else + { + this.pathStyle = PathStyle.valueOf( (String) rawPathStyle ); + } + + this.pathMaskPatterns = (Set) in.readObject(); + this.authoritativeIndex = (Boolean) in.readObject(); + this.createTime = (String) in.readObject(); + this.rescanInProgress = (Boolean) in.readObject(); + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/ArtifactStoreValidateData.java b/src/main/java/org/commonjava/indy/client/model/ArtifactStoreValidateData.java new file mode 100644 index 0000000..6806c1e --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/ArtifactStoreValidateData.java @@ -0,0 +1,116 @@ +/** + * 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.model; + +import java.util.HashMap; +import java.util.Map; + +public class ArtifactStoreValidateData +{ + + private boolean valid; + private String repositoryUrl; + + private StoreKey storeKey; + private Map errors; + + + private ArtifactStoreValidateData() { + } + + + public static class Builder { + + private boolean valid; + private String repositoryUrl; + + private StoreKey storeKey; + private Map errors; + + public Builder(StoreKey storeKey) { + + this.storeKey = storeKey; + this.errors = new HashMap<>(); + this.valid = false; + } + + + public Builder setRepositoryUrl(String repositoryUrl) { + this.repositoryUrl = repositoryUrl; + return this; + } + + public Builder setValid(Boolean valid) { + this.valid = valid; + return this; + } + + public Builder setErrors(Map errors) { + this.errors = errors; + return this; + } + + public ArtifactStoreValidateData build() { + ArtifactStoreValidateData artifactStoreValidateData = new ArtifactStoreValidateData(); + artifactStoreValidateData.valid = this.valid; + artifactStoreValidateData.repositoryUrl = this.repositoryUrl; + artifactStoreValidateData.errors = this.errors; + artifactStoreValidateData.storeKey = this.storeKey; + return artifactStoreValidateData; + } + } + + public boolean isValid() { + return valid; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + + public void setRepositoryUrl(String repositoryUrl) { + this.repositoryUrl = repositoryUrl; + } + + public StoreKey getStoreKey() { + return storeKey; + } + + public void setStoreKey(StoreKey storeKey) { + this.storeKey = storeKey; + } + + public Map getErrors() { + return errors; + } + + public void setErrors(Map errors) { + this.errors = errors; + } + + @Override + public String toString() { + return "{ valid:" + this.valid + + ", repositoryUrl:" + this.repositoryUrl + + ", errors: " + this.errors + + ", storeKey: " + this.storeKey + + "}"; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/BatchDeleteRequest.java b/src/main/java/org/commonjava/indy/client/model/BatchDeleteRequest.java new file mode 100644 index 0000000..05a8165 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/BatchDeleteRequest.java @@ -0,0 +1,118 @@ +/** + * 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.model; + +import io.swagger.annotations.ApiModel; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Objects; +import java.util.Set; + +@ApiModel( description = "Request of batch files that will be removed from the repository. Specifying the folo" + + " trackingID for removing the files uploaded through it, and that's supported in folo client only." ) +public class BatchDeleteRequest + implements Externalizable +{ + + private static final int BATCH_DELETE_REQUEST_VERSION = 1; + + private StoreKey storeKey; + + private String trackingID; + + private Set paths; + + public String getTrackingID() + { + return trackingID; + } + + public void setTrackingID( String trackingID ) + { + this.trackingID = trackingID; + } + + public StoreKey getStoreKey() + { + return storeKey; + } + + public void setStoreKey( StoreKey storeKey ) + { + this.storeKey = storeKey; + } + + public Set getPaths() + { + return paths; + } + + public void setPaths( Set paths ) + { + this.paths = paths; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + return true; + if ( o == null || getClass() != o.getClass() ) + return false; + BatchDeleteRequest that = (BatchDeleteRequest) o; + return Objects.equals( storeKey, that.storeKey ) && Objects.equals( trackingID, that.trackingID ) + && Objects.equals( paths, that.paths ); + } + + @Override + public int hashCode() + { + return Objects.hash( storeKey, trackingID, paths ); + } + + @Override + public String toString() + { + return "BatchDeleteRequest{" + "storeKey=" + storeKey + ", trackingID='" + trackingID + '\'' + ", paths=" + paths + + '}'; + } + + @Override + public void writeExternal( final ObjectOutput out ) throws IOException + { + out.writeObject( BATCH_DELETE_REQUEST_VERSION ); + out.writeObject( storeKey ); + out.writeObject( paths ); + out.writeObject( trackingID ); + } + + @Override + public void readExternal( final ObjectInput in ) throws IOException, ClassNotFoundException + { + int batchDeleteRequestVersion = in.readInt(); + if ( batchDeleteRequestVersion > BATCH_DELETE_REQUEST_VERSION ) + { + throw new IOException( "Cannot deserialize. BatchDeleteRequest version in data stream is: " + batchDeleteRequestVersion + + " but this class can only deserialize up to version: " + BATCH_DELETE_REQUEST_VERSION ); + } + + this.storeKey = (StoreKey) in.readObject(); + this.paths = (Set) in.readObject(); + this.trackingID = (String) in.readObject(); + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/Group.java b/src/main/java/org/commonjava/indy/client/model/Group.java new file mode 100644 index 0000000..a81dc3d --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/Group.java @@ -0,0 +1,205 @@ +/** + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.commonjava.indy.client.model.StoreKey.MAVEN_PKG_KEY; + +@ApiModel( description = "Grouping of other artifact stores, with a defined order to the membership that determines content preference", parent = ArtifactStore.class ) +public class Group + extends ArtifactStore + implements Externalizable +{ + + private static final int STORE_VERSION = 1; + + private List constituents; + + @JsonProperty( "prepend_constituent" ) + private boolean prependConstituent = false; + + public Group() + { + super(); + this.constituents = new ArrayList<>(); + } + + public Group( final String packageType, final String name, final List constituents ) + { + super( packageType, StoreType.group, name ); + this.constituents = new ArrayList<>( constituents ); + } + + @Deprecated + public Group( final String name, final List constituents ) + { + super( MAVEN_PKG_KEY, StoreType.group, name ); + this.constituents = new ArrayList<>( constituents ); + } + + public Group( final String packageType, final String name, final StoreKey... constituents ) + { + super( packageType, StoreType.group, name ); + this.constituents = new ArrayList<>( Arrays.asList( constituents ) ); + } + + @Deprecated + public Group( final String name, final StoreKey... constituents ) + { + super( MAVEN_PKG_KEY, StoreType.group, name ); + this.constituents = new ArrayList<>( Arrays.asList( constituents ) ); + } + + public List getConstituents() + { + return constituents == null ? Collections.emptyList() : Collections.unmodifiableList( constituents ); + } + + public boolean addConstituent( final ArtifactStore store ) + { + if ( store == null ) + { + return false; + } + + return addConstituent( store.getKey() ); + } + + public boolean addConstituent( final StoreKey repository ) + { + if ( repository == null ) + { + return false; + } + + synchronized ( constituents ) + { + if ( constituents.contains( repository ) ) + { + return false; + } + + // We will add new repo as the first member in group if prependConstituent set to true, + // that means this repo may contain the most recent dependencies for the subsequent build. + if ( isPrependConstituent() ) + { + constituents.add( 0, repository ); + return true; + } + else + { + + return constituents.add( repository ); + } + } + } + + public boolean removeConstituent( final ArtifactStore constituent ) + { + return constituent != null && removeConstituent( constituent.getKey() ); + } + + public boolean removeConstituent( final StoreKey repository ) + { + synchronized ( constituents ) + { + return constituents.remove( repository ); + } + } + + public void setConstituents( final List constituents ) + { + synchronized ( this.constituents ) + { + this.constituents.clear(); + this.constituents.addAll( constituents ); + } + } + + public boolean isPrependConstituent() + { + return prependConstituent; + } + + public void setPrependConstituent( boolean prependConstituent ) + { + this.prependConstituent = prependConstituent; + } + + @Override + public String toString() + { + return String.format( "Group[%s]", getName() ); + } + + @Override + public Group copyOf() + { + return copyOf( getPackageType(), getName() ); + } + + @Override + public Group copyOf( final String packageType, final String name ) + { + Group g = new Group( packageType, name, new ArrayList<>( getConstituents() ) ); + g.setPrependConstituent( isPrependConstituent() ); + copyBase( g ); + + return g; + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + super.writeExternal( out ); + + out.writeInt( STORE_VERSION ); + + out.writeObject( constituents ); + + out.writeBoolean( prependConstituent ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + super.readExternal( in ); + + int storeVersion = in.readInt(); + if ( storeVersion > STORE_VERSION ) + { + throw new IOException( "Cannot deserialize. Group version in data stream is: " + storeVersion + + " but this class can only deserialize up to version: " + STORE_VERSION ); + } + + this.constituents = (List) in.readObject(); + + this.prependConstituent = in.readBoolean(); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/HostedRepository.java b/src/main/java/org/commonjava/indy/client/model/HostedRepository.java new file mode 100644 index 0000000..af8f8d2 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/HostedRepository.java @@ -0,0 +1,158 @@ +/** + * 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.model; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import static org.commonjava.indy.client.model.StoreKey.MAVEN_PKG_KEY; +import static org.commonjava.indy.client.model.StoreType.hosted; + +@ApiModel( description = "Hosts artifact content on the local system", parent = ArtifactStore.class ) +public class HostedRepository + extends AbstractRepository + implements Externalizable +{ + + private static final int STORE_VERSION = 1; + + private String storage; + + private int snapshotTimeoutSeconds; + + // if readonly, default is not + @ApiModelProperty( required = false, dataType = "boolean", value = "identify if the hoste repo is readonly" ) + private boolean readonly = false; + + public HostedRepository() + { + super(); + } + + public HostedRepository( final String packageType, final String name ) + { + super( packageType, hosted, name ); + } + + @Deprecated + public HostedRepository( final String name ) + { + super( MAVEN_PKG_KEY, hosted, name ); + } + + @Override + public String toString() + { + return String.format( "HostedRepository [%s]", getName() ); + } + + public int getSnapshotTimeoutSeconds() + { + return snapshotTimeoutSeconds; + } + + public void setSnapshotTimeoutSeconds( final int snapshotTimeoutSeconds ) + { + this.snapshotTimeoutSeconds = snapshotTimeoutSeconds; + } + + public String getStorage() + { + return storage; + } + + public void setStorage( final String storage ) + { + this.storage = storage; + } + + public boolean isReadonly() + { + return readonly; + } + + public void setReadonly( boolean readonly ) + { + this.readonly = readonly; + } + + @Override + public boolean isAuthoritativeIndex() + { + return super.isAuthoritativeIndex() || this.isReadonly(); + } + + @Override + public void setAuthoritativeIndex( boolean authoritativeIndex ) + { + super.setAuthoritativeIndex( authoritativeIndex || this.isReadonly() ); + } + + @Override + public HostedRepository copyOf() + { + return copyOf( getPackageType(), getName() ); + } + + @Override + public HostedRepository copyOf( final String packageType, final String name ) + { + HostedRepository repo = new HostedRepository( packageType, name ); + repo.setStorage( getStorage() ); + repo.setSnapshotTimeoutSeconds( getSnapshotTimeoutSeconds() ); + repo.setReadonly( isReadonly() ); + copyRestrictions( repo ); + copyBase( repo ); + + return repo; + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + super.writeExternal( out ); + + out.writeInt( STORE_VERSION ); + + out.writeObject( storage ); + out.writeInt( snapshotTimeoutSeconds ); + out.writeBoolean( readonly ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + super.readExternal( in ); + + int storeVersion = in.readInt(); + if ( storeVersion > STORE_VERSION ) + { + throw new IOException( "Cannot deserialize. HostedRepository version in data stream is: " + storeVersion + + " but this class can only deserialize up to version: " + STORE_VERSION ); + } + + this.storage = (String) in.readObject(); + this.snapshotTimeoutSeconds = in.readInt(); + this.readonly = in.readBoolean(); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/PathStyle.java b/src/main/java/org/commonjava/indy/client/model/PathStyle.java new file mode 100644 index 0000000..7f40ddc --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/PathStyle.java @@ -0,0 +1,25 @@ +/** + * 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.model; + +/** + * Created by jdcasey on 6/2/16. + */ +public enum PathStyle +{ + plain, + hashed; +} diff --git a/src/main/java/org/commonjava/indy/client/model/RemoteRepository.java b/src/main/java/org/commonjava/indy/client/model/RemoteRepository.java new file mode 100644 index 0000000..f222762 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/RemoteRepository.java @@ -0,0 +1,572 @@ +/** + * 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.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.net.MalformedURLException; +import java.net.URL; + +import static org.commonjava.indy.client.model.StoreKey.MAVEN_PKG_KEY; +import static org.commonjava.indy.client.model.StoreType.remote; + +@ApiModel( description = "Proxy to a remote server's artifact content, with local cache storage.", + parent = ArtifactStore.class ) +public class RemoteRepository + extends AbstractRepository + implements Externalizable +{ + private static final int STORE_VERSION = 1; + + private static final Logger LOGGER = LoggerFactory.getLogger( RemoteRepository.class ); + + public static final String PREFETCH_LISTING_TYPE_HTML = "html"; + + public static final int DEFAULT_MAX_CONNECTIONS = 30; + + @ApiModelProperty( required = true, value = "The remote URL to proxy" ) + @JsonProperty( "url" ) + private String url; + + @JsonProperty( "timeout_seconds" ) + private int timeoutSeconds; + + @JsonProperty( "max_connections" ) + private int maxConnections = DEFAULT_MAX_CONNECTIONS; + + @JsonProperty( "ignore_hostname_verification" ) + private boolean ignoreHostnameVerification; + + @JsonProperty( "nfc_timeout_seconds" ) + private int nfcTimeoutSeconds; + + private String host; + + private int port; + + private String user; + + private String password; + + @JsonProperty( "is_passthrough" ) + private boolean passthrough; + + @JsonProperty( "cache_timeout_seconds" ) + private int cacheTimeoutSeconds; + + @JsonProperty( "metadata_timeout_seconds" ) + private int metadataTimeoutSeconds; + + @JsonProperty( "key_password" ) + private String keyPassword; + + @JsonProperty( "key_certificate_pem" ) + private String keyCertificatePem; + + @JsonProperty( "server_certificate_pem" ) + private String serverCertificatePem; + + @JsonProperty( "proxy_host" ) + private String proxyHost; + + @JsonProperty( "proxy_port" ) + private int proxyPort; + + @JsonProperty( "proxy_user" ) + private String proxyUser; + + @JsonProperty( "proxy_password" ) + private String proxyPassword; + + @JsonProperty( "server_trust_policy" ) + private String serverTrustPolicy; + + @ApiModelProperty( + value = "Integer to indicate the pre-fetching priority of the remote, higher means more eager to do the pre-fetching of the content in the repo, 0 or below means disable the pre-fecthing." ) + @JsonProperty( "prefetch_priority" ) + private Integer prefetchPriority = 0; + + @ApiModelProperty( value = "Indicates if the remote needs to do rescan after prefetch" ) + @JsonProperty( "prefetch_rescan" ) + private boolean prefetchRescan = false; + + @ApiModelProperty( value = "The prefetch listing type, should be html or koji" ) + @JsonProperty( "prefetch_listing_type" ) + private String prefetchListingType = PREFETCH_LISTING_TYPE_HTML; + + + @JsonProperty( "prefetch_rescan_time" ) + private String prefetchRescanTimestamp; + + public RemoteRepository() + { + super(); + } + + @Deprecated + public RemoteRepository( final String name, final String remoteUrl ) + { + this( MAVEN_PKG_KEY, name, remoteUrl ); + } + + public RemoteRepository( final String packageType, String name, String remoteUrl ) + { + super( packageType, remote, name ); + this.url = remoteUrl; + calculateFields(); + } + + public String getUrl() + { + calculateIfNeeded(); + return url; + } + + public void setUrl( final String url ) + { + this.url = url; + calculateFields(); + } + + public String getUser() + { + calculateIfNeeded(); + return user; + } + + private void calculateIfNeeded() + { + if ( host == null ) + { + calculateFields(); + } + } + + public void setUser( final String user ) + { + this.user = user; + } + + public String getPassword() + { + calculateIfNeeded(); + return password; + } + + public void setPassword( final String password ) + { + this.password = password; + } + + public String getHost() + { + calculateIfNeeded(); + return host; + } + + public void setHost( final String host ) + { + this.host = host; + } + + public int getPort() + { + calculateIfNeeded(); + return port; + } + + public void setPort( final int port ) + { + this.port = port; + } + + public void calculateFields() + { + URL url = null; + try + { + url = new URL( this.url ); + } + catch ( final MalformedURLException e ) + { + LOGGER.warn( String.format( "Failed to parse repository URL: '%s'. Reason: %s", this.url, e.getMessage() ) ); + } + + if ( url == null ) + { + return; + } + + final String userInfo = url.getUserInfo(); + if ( userInfo != null && user == null && password == null ) + { + user = userInfo; + password = null; + + int idx = userInfo.indexOf( ':' ); + if ( idx > 0 ) + { + user = userInfo.substring( 0, idx ); + password = userInfo.substring( idx + 1 ); + + final StringBuilder sb = new StringBuilder(); + idx = this.url.indexOf( "://" ); + sb.append( this.url.substring( 0, idx + 3 ) ); + + idx = this.url.indexOf( "@" ); + if ( idx > 0 ) + { + sb.append( this.url.substring( idx + 1 ) ); + } + + this.url = sb.toString(); + } + } + + host = url.getHost(); + if ( url.getPort() < 0 ) + { + port = url.getProtocol().equals( "https" ) ? 443 : 80; + } + else + { + port = url.getPort(); + } + } + + public int getTimeoutSeconds() + { + return timeoutSeconds; + } + + public void setTimeoutSeconds( final int timeoutSeconds ) + { + this.timeoutSeconds = timeoutSeconds; + } + + public int getMaxConnections() + { + return maxConnections; + } + + public void setMaxConnections( int maxConnections ) + { + this.maxConnections = maxConnections; + } + + public boolean isIgnoreHostnameVerification() + { + return ignoreHostnameVerification; + } + + public void setIgnoreHostnameVerification( boolean ignoreHostnameVerification ) + { + this.ignoreHostnameVerification = ignoreHostnameVerification; + } + + @Override + public String toString() + { + return String.format( + "RemoteRepository [%s, %s]", + getName(), url ); + } + + public boolean isPassthrough() + { + return passthrough; + } + + public void setPassthrough( final boolean passthrough ) + { + this.passthrough = passthrough; + } + + public int getCacheTimeoutSeconds() + { + return cacheTimeoutSeconds; + } + + public void setCacheTimeoutSeconds( final int cacheTimeoutSeconds ) + { + this.cacheTimeoutSeconds = cacheTimeoutSeconds; + } + + public int getMetadataTimeoutSeconds() + { + return metadataTimeoutSeconds; + } + + public void setMetadataTimeoutSeconds( int metadataTimeoutSeconds ) + { + this.metadataTimeoutSeconds = metadataTimeoutSeconds; + } + + public void setKeyPassword( final String keyPassword ) + { + this.keyPassword = keyPassword; + } + + @JsonIgnore + public String getKeyPassword() + { + return keyPassword; + } + + public void setKeyCertPem( final String keyCertificatePem ) + { + this.keyCertificatePem = keyCertificatePem; + } + + @JsonIgnore + public String getKeyCertPem() + { + return keyCertificatePem; + } + + public void setServerCertPem( final String serverCertificatePem ) + { + this.serverCertificatePem = serverCertificatePem; + } + + @JsonIgnore + public String getServerCertPem() + { + return serverCertificatePem; + } + + public String getProxyHost() + { + return proxyHost; + } + + public int getProxyPort() + { + return proxyPort; + } + + public String getProxyUser() + { + return proxyUser; + } + + public String getProxyPassword() + { + return proxyPassword; + } + + public void setProxyHost( final String proxyHost ) + { + this.proxyHost = proxyHost; + } + + public void setProxyPort( final int proxyPort ) + { + this.proxyPort = proxyPort; + } + + public void setProxyUser( final String proxyUser ) + { + this.proxyUser = proxyUser; + } + + public void setProxyPassword( final String proxyPassword ) + { + this.proxyPassword = proxyPassword; + } + + public int getNfcTimeoutSeconds() + { + return nfcTimeoutSeconds; + } + + public void setNfcTimeoutSeconds( final int nfcTimeoutSeconds ) + { + this.nfcTimeoutSeconds = nfcTimeoutSeconds; + } + + public String getServerTrustPolicy() + { + return serverTrustPolicy; + } + + public void setServerTrustPolicy( final String serverTrustPolicy ) + { + this.serverTrustPolicy = serverTrustPolicy; + } + + public Integer getPrefetchPriority() + { + return prefetchPriority; + } + + public void setPrefetchPriority( Integer prefetchPriority ) + { + this.prefetchPriority = prefetchPriority; + } + + public boolean isPrefetchRescan() + { + return prefetchRescan; + } + + public void setPrefetchRescan( boolean prefetchRescan ) + { + this.prefetchRescan = prefetchRescan; + } + + public String getPrefetchListingType() + { + return prefetchListingType; + } + + public void setPrefetchListingType( String prefetchListingType ) + { + this.prefetchListingType = prefetchListingType; + } + + @JsonIgnore + public String getPrefetchRescanTimestamp() + { + return prefetchRescanTimestamp; + } + + public void setPrefetchRescanTimestamp( String prefetchRescanTimestamp ) + { + this.prefetchRescanTimestamp = prefetchRescanTimestamp; + } + + @Override + public RemoteRepository copyOf() + { + return copyOf( getPackageType(), getName() ); + } + + @Override + public RemoteRepository copyOf( String packageType, String name ) + { + RemoteRepository repo = new RemoteRepository( getPackageType(), name, getUrl() ); + repo.setServerTrustPolicy( getServerTrustPolicy() ); + repo.setKeyPassword( getKeyPassword() ); + repo.setKeyCertPem( getKeyCertPem() ); + repo.setServerCertPem( getServerCertPem() ); + repo.setCacheTimeoutSeconds( getCacheTimeoutSeconds() ); + repo.setMetadataTimeoutSeconds( getMetadataTimeoutSeconds() ); + repo.setNfcTimeoutSeconds( getNfcTimeoutSeconds() ); + repo.setPassthrough( isPassthrough() ); + repo.setProxyHost( getProxyHost() ); + repo.setHost( getHost() ); + repo.setPort( getPort() ); + repo.setProxyPort( getProxyPort() ); + repo.setProxyPassword( getProxyPassword() ); + repo.setProxyUser( getProxyUser() ); + repo.setTimeoutSeconds( getTimeoutSeconds() ); + repo.setMaxConnections( getMaxConnections() ); + repo.setIgnoreHostnameVerification( isIgnoreHostnameVerification() ); + repo.setUser( getUser() ); + repo.setPassword( getPassword() ); + repo.setPrefetchListingType( getPrefetchListingType() ); + repo.setPrefetchPriority( getPrefetchPriority() ); + repo.setPrefetchRescan( isPrefetchRescan() ); + repo.setPrefetchRescanTimestamp( getPrefetchRescanTimestamp() ); + + copyRestrictions( repo ); + copyBase( repo ); + return repo; + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + super.writeExternal( out ); + + out.writeInt( STORE_VERSION ); + + out.writeObject( url ); + out.writeInt( timeoutSeconds ); + out.writeInt( maxConnections ); + out.writeBoolean( ignoreHostnameVerification ); + out.writeInt( nfcTimeoutSeconds ); + out.writeObject( host ); + out.writeInt( port ); + out.writeObject( user ); + out.writeObject( password ); + out.writeBoolean( passthrough ); + out.writeInt( cacheTimeoutSeconds ); + out.writeInt( metadataTimeoutSeconds ); + out.writeObject( keyPassword ); + out.writeObject( keyCertificatePem ); + out.writeObject( serverCertificatePem ); + out.writeObject( proxyHost ); + out.writeInt( proxyPort ); + out.writeObject( proxyUser ); + out.writeObject( proxyPassword ); + out.writeObject( serverTrustPolicy ); + out.writeObject( prefetchPriority ); + out.writeBoolean( prefetchRescan ); + out.writeObject( prefetchListingType ); + out.writeObject( prefetchRescanTimestamp ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + super.readExternal( in ); + + int storeVersion = in.readInt(); + if ( storeVersion > STORE_VERSION ) + { + throw new IOException( "Cannot deserialize. RemoteRepository version in data stream is: " + storeVersion + + " but this class can only deserialize up to version: " + STORE_VERSION ); + } + + this.url = (String) in.readObject(); + this.timeoutSeconds = in.readInt(); + this.maxConnections = in.readInt(); + this.ignoreHostnameVerification = in.readBoolean(); + this.nfcTimeoutSeconds = in.readInt(); + this.host = (String) in.readObject(); + this.port = in.readInt(); + this.user = (String) in.readObject(); + this.password = (String) in.readObject(); + this.passthrough = in.readBoolean(); + this.cacheTimeoutSeconds = in.readInt(); + this.metadataTimeoutSeconds = in.readInt(); + this.keyPassword = (String) in.readObject(); + this.keyCertificatePem = (String) in.readObject(); + this.serverCertificatePem = (String) in.readObject(); + this.proxyHost = (String) in.readObject(); + this.proxyPort = in.readInt(); + this.proxyUser = (String) in.readObject(); + this.proxyPassword = (String) in.readObject(); + this.serverTrustPolicy = (String) in.readObject(); + this.prefetchPriority = (Integer) in.readObject(); + this.prefetchRescan = in.readBoolean(); + this.prefetchListingType = (String) in.readObject(); + this.prefetchRescanTimestamp = (String) in.readObject(); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/StoreKey.java b/src/main/java/org/commonjava/indy/client/model/StoreKey.java new file mode 100644 index 0000000..18e5b85 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/StoreKey.java @@ -0,0 +1,240 @@ +/** + * 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.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.commonjava.indy.client.util.PackageTypeConstants.PKG_TYPE_MAVEN; + +public final class StoreKey + implements Comparable, Externalizable +{ + + public static final String MAVEN_PKG_KEY = PKG_TYPE_MAVEN; + + private static final int VERSION = 1; + + private String packageType; + + private StoreType type; + + private String name; + + public StoreKey(){} + + public StoreKey(final String packageType, final StoreType type, final String name ) + { + this.packageType = packageType; + this.type = type; + this.name = name; + } + + public String getPackageType() + { + return packageType; + } + + public StoreType getType() + { + return type; + } + + public String getName() + { + return name; + } + + @Override + public String toString() + { + return packageType + ":" + type.name() + ":" + name; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( packageType == null ) ? 7 : packageType.hashCode() ); + result = prime * result + ( ( name == null ) ? 13 : name.hashCode() ); + result = prime * result + ( ( type == null ) ? 17 : type.name().hashCode() ); + return result; + } + + @Override + public boolean equals( final Object obj ) + { + if ( this == obj ) + { + return true; + } + if ( obj == null ) + { + return false; + } + if ( getClass() != obj.getClass() ) + { + return false; + } + final StoreKey other = (StoreKey) obj; + if ( packageType == null ) + { + if ( other.packageType != null ) + { + return false; + } + } + else if ( !packageType.equals( other.packageType ) ) + { + return false; + } + if ( name == null ) + { + if ( other.name != null ) + { + return false; + } + } + else if ( !name.equals( other.name ) ) + { + return false; + } + return type == other.type; + } + + public static StoreKey fromString( final String id ) + { + Logger logger = LoggerFactory.getLogger( StoreKey.class ); + logger.debug( "Parsing raw string: '{}' to StoreKey", id ); + + String[] parts = id.split(":"); + + logger.debug( "Got {} parts: {}", parts.length, Arrays.asList( parts ) ); + + String packageType = null; + String name; + StoreType type = null; + + // FIXME: We need to get to a point where it's safe for this to be an error and not default to maven. + if ( parts.length < 2 ) + { + packageType = MAVEN_PKG_KEY; + type = StoreType.remote; + name = id; + } + else if ( parts.length < 3 || isBlank( parts[0] ) ) + { + packageType = MAVEN_PKG_KEY; + type = StoreType.get( parts[0] ); + name = parts[1]; + } + else + { + packageType = parts[0]; + type = StoreType.get( parts[1] ); + name = parts[2]; + } + + if ( type == null ) + { + throw new IllegalArgumentException( "Invalid StoreType: " + parts[1] ); + } + + // logger.info( "parsed store-key with type: '{}' and name: '{}'", type, name ); + + return new StoreKey( packageType, type, name ); + } + + @Override + public int compareTo( final StoreKey o ) + { + int comp = packageType.compareTo( o.packageType ); + if ( comp == 0 ) + { + comp = type.compareTo( o.type ); + } + + if ( comp == 0 ) + { + comp = name.compareTo( o.name ); + } + + return comp; + } + + private static final ConcurrentHashMap deduplications = new ConcurrentHashMap<>(); + + public static StoreKey dedupe( StoreKey key ) + { + StoreKey result = deduplications.get( key ); + if ( result == null ) + { + deduplications.put( key, key ); + result = key; + } + + return result; + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + out.writeInt( VERSION ); + + out.writeObject( packageType ); + + if ( type == null ) + { + out.writeObject( null ); + } + else + { + out.writeObject( type.name() ); + } + + out.writeObject( name ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + int keyVersion = in.readInt(); + + this.packageType = (String) in.readObject(); + + Object rawType = in.readObject(); + if ( rawType == null ) + { + this.type = null; + } + else + { + this.type = StoreType.valueOf( (String) rawType ); + } + + this.name = (String) in.readObject(); + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/StoreType.java b/src/main/java/org/commonjava/indy/client/model/StoreType.java new file mode 100644 index 0000000..376669c --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/StoreType.java @@ -0,0 +1,98 @@ +/** + * 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.model; + +import java.util.HashSet; +import java.util.Set; + +//@ApiClass( description = "Enumeration of types of artifact storage on the system. This forms half of the 'primary key' for each store (the other half is the store's name).", value = "Type of artifact storage." ) +public enum StoreType +{ + group( false, "group", "groups", "g" ), remote( false, "remote", "remotes", "repository", "repositories", + "r" ), hosted( true, "hosted", "hosted", "deploy", "deploys", "deploy_point", "h", "d" ); + + // private static final Logger logger = new Logger( StoreType.class ); + + private final boolean writable; + + private final String singular; + + private final String plural; + + private final Set aliases; + + StoreType( final boolean writable, final String singular, + final String plural, final String... aliases ) + { + this.writable = writable; + this.singular = singular; + this.plural = plural; + + final Set a = new HashSet(); + a.add( singular ); + a.add( plural ); + for ( final String alias : aliases ) + { + a.add( alias.toLowerCase() ); + } + + this.aliases = a; + } + + public String pluralEndpointName() + { + return plural; + } + + public String singularEndpointName() + { + return singular; + } + + public boolean isWritable() + { + return writable; + } + + public static StoreType get( final String typeStr ) + { + if ( typeStr == null ) + { + return null; + } + + final String type = typeStr.trim() + .toLowerCase(); + if ( type.length() < 1 ) + { + return null; + } + + for ( final StoreType st : values() ) + { + // logger.info( "Checking '{}' vs name: '{}' and aliases: {}", type, st.name(), join( st.aliases, ", " ) ); + if ( st.name() + .equalsIgnoreCase( type ) || st.aliases.contains( type ) ) + { + return st; + } + } + + return null; + } + + +} diff --git a/src/main/java/org/commonjava/indy/client/model/browse/ContentBrowseResult.java b/src/main/java/org/commonjava/indy/client/model/browse/ContentBrowseResult.java new file mode 100644 index 0000000..26439c4 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/browse/ContentBrowseResult.java @@ -0,0 +1,190 @@ +/** + * 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.model.browse; + +import org.commonjava.indy.client.model.StoreKey; +import java.util.List; +import java.util.Set; + +public class ContentBrowseResult +{ + private StoreKey storeKey; + + private String parentUrl; + + private String parentPath; + + private String path; + + private String storeBrowseUrl; + + private String storeContentUrl; + + private String baseBrowseUrl; + + private String baseContentUrl; + + private List sources; + + private List listingUrls; + + public StoreKey getStoreKey() + { + return storeKey; + } + + public void setStoreKey( StoreKey storeKey ) + { + this.storeKey = storeKey; + } + + public String getParentUrl() + { + return parentUrl; + } + + public void setParentUrl( String parentUrl ) + { + this.parentUrl = parentUrl; + } + + public String getParentPath() + { + return parentPath; + } + + public void setParentPath( String parentPath ) + { + this.parentPath = parentPath; + } + + public String getPath() + { + return path; + } + + public void setPath( String path ) + { + this.path = path; + } + + public String getStoreBrowseUrl() + { + return storeBrowseUrl; + } + + public void setStoreBrowseUrl( String storeBrowseUrl ) + { + this.storeBrowseUrl = storeBrowseUrl; + } + + public String getStoreContentUrl() + { + return storeContentUrl; + } + + public void setStoreContentUrl( String storeContentUrl ) + { + this.storeContentUrl = storeContentUrl; + } + + public String getBaseBrowseUrl() + { + return baseBrowseUrl; + } + + public void setBaseBrowseUrl( String baseBrowseUrl ) + { + this.baseBrowseUrl = baseBrowseUrl; + } + + public String getBaseContentUrl() + { + return baseContentUrl; + } + + public void setBaseContentUrl( String baseContentUrl ) + { + this.baseContentUrl = baseContentUrl; + } + + public List getSources() + { + return sources; + } + + public void setSources( List sources ) + { + this.sources = sources; + } + + public List getListingUrls() + { + return listingUrls; + } + + public void setListingUrls( List listingUrls){ + this.listingUrls = listingUrls; + } + + public static class ListingURLResult + { + private String path; + + private String listingUrl; + + private Set sources; + + public ListingURLResult(){} + + public ListingURLResult( String path, String listingUrl, Set sources ) + { + this.path = path; + this.listingUrl = listingUrl; + this.sources = sources; + } + + public String getPath() + { + return path; + } + + public String getListingUrl() + { + return listingUrl; + } + + public Set getSources() + { + return sources; + } + + public void setPath( String path ) + { + this.path = path; + } + + public void setListingUrl( String listingUrl ) + { + this.listingUrl = listingUrl; + } + + public void setSources( Set sources ) + { + this.sources = sources; + } + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/folo/TrackedContentDTO.java b/src/main/java/org/commonjava/indy/client/model/folo/TrackedContentDTO.java new file mode 100644 index 0000000..3bcc0f3 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/folo/TrackedContentDTO.java @@ -0,0 +1,96 @@ +/** + * 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.model.folo; + +import io.swagger.annotations.ApiModelProperty; +import java.util.Set; + +public class TrackedContentDTO +{ + + @ApiModelProperty( "Session key (specified by the user) to track this record." ) + private TrackingKey key; + + private Set uploads; + + private Set downloads; + + public TrackedContentDTO() + { + } + + public TrackedContentDTO( final TrackingKey key, final Set uploads, + final Set downloads ) + { + this.key = key; + this.uploads = uploads; + this.downloads = downloads; + } + + public TrackingKey getKey() + { + return key; + } + + public void setKey( final TrackingKey key ) + { + this.key = key; + } + + public Set getUploads() + { + return uploads; + } + + public void setUploads( final Set uploads ) + { + this.uploads = uploads; + } + + public Set getDownloads() + { + return downloads; + } + + public void setDownloads( final Set downloads ) + { + this.downloads = downloads; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( !( o instanceof TrackedContentDTO ) ) + { + return false; + } + + TrackedContentDTO that = (TrackedContentDTO) o; + + return getKey() != null ? getKey().equals( that.getKey() ) : that.getKey() == null; + + } + + @Override + public int hashCode() + { + return getKey() != null ? getKey().hashCode() : 0; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/folo/TrackedContentEntryDTO.java b/src/main/java/org/commonjava/indy/client/model/folo/TrackedContentEntryDTO.java new file mode 100644 index 0000000..facf331 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/folo/TrackedContentEntryDTO.java @@ -0,0 +1,252 @@ +/** + * 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.model.folo; + +import io.swagger.annotations.ApiModelProperty; +import org.commonjava.indy.client.model.AccessChannel; +import org.commonjava.indy.client.model.StoreKey; +import java.util.Set; + +public class TrackedContentEntryDTO + implements Comparable +{ + + @ApiModelProperty( value = "The Indy key for the repository/group this where content was stored.", + allowableValues = "remote:, hosted:, group:" ) + private StoreKey storeKey; + + @ApiModelProperty( value = "Type of content access, whether \"normal\" content API or generic HTTP proxy.", + allowableValues = "GENERIC_PROXY, MAVEN" ) + private AccessChannel accessChannel; + + private String path; + + @ApiModelProperty( value = "If resolved from a remote repository, this is the origin URL, otherwise empty/null." ) + private String originUrl; + + @ApiModelProperty( value = "URL to this path on the local Indy instance." ) + private String localUrl; + + private String md5; + + private String sha256; + + private String sha1; + + private Long size; + + private Set timestamps; + + public TrackedContentEntryDTO() + { + } + + public TrackedContentEntryDTO( final StoreKey storeKey, final AccessChannel accessChannel, final String path ) + { + this.storeKey = storeKey; + setAccessChannel( accessChannel ); + this.path = path.startsWith( "/" ) ? path : "/" + path; + } + + public String getOriginUrl() + { + return originUrl; + } + + public void setOriginUrl( final String originUrl ) + { + this.originUrl = originUrl; + } + + public String getLocalUrl() + { + return localUrl; + } + + public void setLocalUrl( final String localUrl ) + { + this.localUrl = localUrl; + } + + public String getMd5() + { + return md5; + } + + public void setMd5( final String md5 ) + { + this.md5 = md5; + } + + public String getSha256() + { + return sha256; + } + + public void setSha256( final String sha256 ) + { + this.sha256 = sha256; + } + + public void setSha1( final String sha1 ) + { + this.sha1 = sha1; + } + + public String getSha1() + { + return sha1; + } + public StoreKey getStoreKey() + { + return storeKey; + } + + public void setStoreKey( final StoreKey storeKey ) + { + this.storeKey = storeKey; + } + + public AccessChannel getAccessChannel() + { + return accessChannel; + } + + public void setAccessChannel( final AccessChannel accessChannel ) + { + this.accessChannel = accessChannel == AccessChannel.MAVEN_REPO ? AccessChannel.NATIVE : accessChannel; + } + + public String getPath() + { + return path; + } + + public void setPath( final String path ) + { + this.path = path.startsWith( "/" ) ? path : "/" + path; + } + + public Long getSize() + { + return size; + } + + public void setSize( final Long size ) + { + this.size = size; + } + + @Override + public int compareTo( final TrackedContentEntryDTO other ) + { + int comp = storeKey.compareTo( other.getStoreKey() ); + if ( comp == 0 ) + { + comp = accessChannel.compareTo( other.getAccessChannel() ); + } + if ( comp == 0 ) + { + comp = path.compareTo( other.getPath() ); + } + + return comp; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( path == null ) ? 0 : path.hashCode() ); + result = prime * result + ( ( storeKey == null ) ? 0 : storeKey.hashCode() ); + result = prime * result + ( ( accessChannel == null ) ? 0 : accessChannel.hashCode() ); + return result; + } + + @Override + public boolean equals( final Object obj ) + { + if ( this == obj ) + { + return true; + } + if ( obj == null ) + { + return false; + } + if ( getClass() != obj.getClass() ) + { + return false; + } + final TrackedContentEntryDTO other = (TrackedContentEntryDTO) obj; + if ( path == null ) + { + if ( other.path != null ) + { + return false; + } + } + else if ( !path.equals( other.path ) ) + { + return false; + } + if ( storeKey == null ) + { + if ( other.storeKey != null ) + { + return false; + } + } + else if ( !storeKey.equals( other.storeKey ) ) + { + return false; + } + if ( accessChannel == null ) + { + if ( other.accessChannel != null ) + { + return false; + } + } + // this is complicated by the transition from using MAVEN_REPO to NATIVE for non-proxy access channels. + else if ( !accessChannel.equals( other.accessChannel ) && !( accessChannel == AccessChannel.NATIVE + && other.accessChannel == AccessChannel.MAVEN_REPO ) && !( accessChannel == AccessChannel.MAVEN_REPO + && other.accessChannel == AccessChannel.NATIVE ) ) + { + return false; + } + + return true; + } + + @Override + public String toString() + { + return String.format( "TrackedContentEntryDTO [\n storeKey=%s\n accessChannel=%s\n path=%s\n originUrl=%s\n localUrl=%s\n size=%d\n md5=%s\n sha256=%s\n]", + storeKey, accessChannel, path, originUrl, localUrl, size, md5, sha256 ); + } + + public Set getTimestamps() + { + return timestamps; + } + + public void setTimestamps( final Set timestamps ) + { + this.timestamps = timestamps; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/folo/TrackingIdsDTO.java b/src/main/java/org/commonjava/indy/client/model/folo/TrackingIdsDTO.java new file mode 100644 index 0000000..feae063 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/folo/TrackingIdsDTO.java @@ -0,0 +1,56 @@ +/** + * 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.model.folo; + +import java.util.Set; + +public class TrackingIdsDTO +{ + + private Set inProgress; + + private Set sealed; + + public TrackingIdsDTO() + { + } + + public TrackingIdsDTO( final Set inProgress, final Set sealed ) + { + this.inProgress = inProgress; + this.sealed = sealed; + } + + public Set getInProgress() + { + return inProgress; + } + + public void setInProgress( Set inProgress ) + { + this.inProgress = inProgress; + } + + public Set getSealed() + { + return sealed; + } + + public void setSealed( Set sealed ) + { + this.sealed = sealed; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/folo/TrackingKey.java b/src/main/java/org/commonjava/indy/client/model/folo/TrackingKey.java new file mode 100644 index 0000000..a9e2752 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/folo/TrackingKey.java @@ -0,0 +1,111 @@ +/** + * 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.model.folo; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +public class TrackingKey + implements Externalizable +{ + + private String id; + + public TrackingKey() + { + } + + protected void setId( final String id ) + { + if ( id == null ) + { + throw new NullPointerException( "tracking id cannot be null." ); + } + + this.id = id; + } + + public TrackingKey( final String id ) + { + setId( id ); + } + + public String getId() + { + return id; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( id == null ) ? 0 : id.hashCode() ); + return result; + } + + @Override + public boolean equals( final Object obj ) + { + if ( this == obj ) + { + return true; + } + if ( obj == null ) + { + return false; + } + if ( getClass() != obj.getClass() ) + { + return false; + } + final TrackingKey other = (TrackingKey) obj; + if ( id == null ) + { + if ( other.id != null ) + { + return false; + } + } + else if ( !id.equals( other.id ) ) + { + return false; + } + return true; + } + + @Override + public String toString() + { + return String.format( "TrackingKey [%s]", id ); + } + + @Override + public void writeExternal( final ObjectOutput out ) + throws IOException + { + out.writeObject( id ); + } + + @Override + public void readExternal( final ObjectInput in ) + throws IOException, ClassNotFoundException + { + id = (String) in.readObject(); + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/koji/KojiRepairRequest.java b/src/main/java/org/commonjava/indy/client/model/koji/KojiRepairRequest.java new file mode 100644 index 0000000..d74174e --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/koji/KojiRepairRequest.java @@ -0,0 +1,76 @@ +/** + * 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.model.koji; + +import io.swagger.annotations.ApiModelProperty; +import org.commonjava.indy.client.model.StoreKey; + +/** + * Request to repair Koji remote stores. If source is a group, all repositories in the group are to be repaired. + * + * @author ruhan + * + */ +public class KojiRepairRequest +{ + @ApiModelProperty( value = "Koji repository key to repair (formatted as: '{maven}:{remote,group}:name')", required = true ) + private StoreKey source; + + @ApiModelProperty( value = "Repair arguments" ) + private String args; + + @ApiModelProperty( value = "Get repair report ONLY, not modify anything." ) + private boolean dryRun; + + public KojiRepairRequest() {} + + public KojiRepairRequest( final StoreKey source, boolean dryRun ) + { + this.source = source; + this.dryRun = dryRun; + } + + public StoreKey getSource() + { + return source; + } + + public void setSource( final StoreKey source ) + { + this.source = source; + } + + public String getArgs() + { + return args; + } + + public void setArgs( String args ) + { + this.args = args; + } + + public boolean isDryRun() + { + return dryRun; + } + + public void setDryRun( final boolean dryRun ) + { + this.dryRun = dryRun; + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/koji/KojiRepairResult.java b/src/main/java/org/commonjava/indy/client/model/koji/KojiRepairResult.java new file mode 100644 index 0000000..0245be1 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/koji/KojiRepairResult.java @@ -0,0 +1,238 @@ +/** + * 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.model.koji; + +import io.swagger.annotations.ApiModelProperty; +import org.commonjava.indy.client.model.StoreKey; +import java.util.ArrayList; +import java.util.List; + +/** + * Contains the result of a repair attempt. If it is a success, the error will be null. + * + * @author ruhan + * + */ +public class KojiRepairResult +{ + @ApiModelProperty( "Original request" ) + private KojiRepairRequest request; + + @ApiModelProperty( "Error message if failed" ) + private String error; + + @ApiModelProperty( "Exception object if failed because of exception" ) + private Exception exception; + + @ApiModelProperty( "Result entries if succeeded" ) + private List results; + + public KojiRepairResult() {} + + public KojiRepairResult( KojiRepairRequest request ) + { + this.request = request; + this.results = new ArrayList<>(); + } + + public KojiRepairResult withError( String error ) + { + this.error = error; + this.results.clear(); + return this; + } + + public KojiRepairResult withError( String error, Exception e ) + { + this.exception = e; + return withError( error ); + } + + public KojiRepairResult withNoChange( StoreKey storeKey ) + { + results.add( new RepairResult( storeKey ) ); + return this; + } + + public KojiRepairResult withIgnore( StoreKey storeKey ) + { + RepairResult result = new RepairResult( storeKey ); + result.setIgnored( true ); + results.add( result ); + return this; + } + + public KojiRepairResult withResult( RepairResult result ) + { + results.add( result ); + return this; + } + + public boolean succeeded() + { + return error == null; + } + + public String getError() + { + return error; + } + + public KojiRepairRequest getRequest() + { + return request; + } + + public List getResults() + { + return results; + } + + public Exception getException() + { + return exception; + } + + /** + * Repair result object of one store + */ + public static class RepairResult + { + private StoreKey storeKey; + + private List changes; + + private boolean ignored; + + private Exception exception; + + public RepairResult() {} + + public RepairResult( StoreKey storeKey ) + { + this.storeKey = storeKey; + this.changes = new ArrayList<>(); + } + + public RepairResult( StoreKey storeKey, Exception e ) + { + this( storeKey ); + this.exception = e; + } + + public StoreKey getStoreKey() + { + return storeKey; + } + + public List getChanges() + { + return changes; + } + + public boolean isIgnored() + { + return ignored; + } + + public void setIgnored( boolean ignored ) + { + this.ignored = ignored; + } + + public void withPropertyChange( String name, Object originalValue, Object value ) + { + changes.add( new PropertyChange( name, originalValue, value ) ); + } + + public boolean isChanged() + { + return changes != null && !changes.isEmpty(); + } + + public Exception getException() + { + return exception; + } + + @Override + public String toString() + { + return "RepairResult{" + "storeKey=" + storeKey + ", changes=" + changes + ", ignored=" + ignored + + ", exception=" + exception + '}'; + } + } + + public static class PropertyChange + { + private String name; + + private Object originalValue; + + private Object value; + + public PropertyChange() {} + + public PropertyChange( String name ) + { + this.name = name; + } + + public PropertyChange( String name, Object originalValue, Object value ) + { + this.name = name; + this.originalValue = originalValue; + this.value = value; + } + + public String getName() + { + return name; + } + + public boolean isChanged() + { + return (originalValue == null && value != null) || !originalValue.equals( value ); + } + + public Object getOriginalValue() + { + return originalValue; + } + + public void setOriginalValue( Object originalValue ) + { + this.originalValue = originalValue; + } + + public Object getValue() + { + return value; + } + + public void setValue( Object value ) + { + this.value = value; + } + + @Override + public String toString() + { + return "PropertyChange{" + "name='" + name + '\'' + ", originalValue=" + originalValue + ", value=" + value + + '}'; + } + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteRequest.java b/src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteRequest.java new file mode 100644 index 0000000..e850ca1 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteRequest.java @@ -0,0 +1,80 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; +import java.util.UUID; + +/** + * Created by ruhan on 12/5/18. + */ +public abstract class AbstractPromoteRequest implements PromoteRequest +{ + @ApiModelProperty( value="Asynchronous call. A callback url is needed when it is true." ) + protected boolean async; + + @ApiModelProperty( "Optional promotion Id" ) + protected String promotionId = UUID.randomUUID().toString(); // default + + @ApiModelProperty( "Optional tracking Id" ) + protected String trackingId; + + @ApiModelProperty( value="Callback which is used to send the promotion result." ) + protected CallbackTarget callback; + + public String getTrackingId() + { + return trackingId; + } + + public T setTrackingId(String trackingId) + { + this.trackingId = trackingId; + return (T) this; + } + + @Override + public boolean isAsync() + { + return async; + } + + public String getPromotionId() + { + return promotionId; + } + + public void setPromotionId( String promotionId ) + { + this.promotionId = promotionId; + } + + @Override + public CallbackTarget getCallback() + { + return callback; + } + + public void setAsync( boolean async ) + { + this.async = async; + } + + public void setCallback( CallbackTarget callback ) + { + this.callback = callback; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteResult.java b/src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteResult.java new file mode 100644 index 0000000..a54bad1 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/AbstractPromoteResult.java @@ -0,0 +1,93 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; + +/** + * Created by ruhan on 12/5/18. + */ +public abstract class AbstractPromoteResult +{ + public static final String DONE = "DONE"; + + public static final String ACCEPTED = "ACCEPTED"; + + @ApiModelProperty( "Result code" ) + protected String resultCode = DONE; // default + + @ApiModelProperty( "Result of validation rule executions, if applicable" ) + protected ValidationResult validations; + + @ApiModelProperty( "Error message, if promotion failed" ) + protected String error; + + protected AbstractPromoteResult(){} + + protected AbstractPromoteResult( final String error, final ValidationResult validations ) + { + this.validations = validations; + if ( error != null ) + { + this.error = error; + } + else + { + this.error = ( validations == null || validations.isValid() ) ? null : "Promotion validation failed"; + } + } + + public String getResultCode() + { + return resultCode; + } + + public void setResultCode( String resultCode ) + { + this.resultCode = resultCode; + } + + public T accepted() + { + this.resultCode = ACCEPTED; + return (T) this; + } + + public boolean succeeded() + { + return error == null && ( validations == null || validations.isValid() ); + } + + public String getError() + { + return error; + } + + public void setError( final String error ) + { + this.error = error; + } + + public ValidationResult getValidations() + { + return validations; + } + + public void setValidations( ValidationResult validations ) + { + this.validations = validations; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/CallbackTarget.java b/src/main/java/org/commonjava/indy/client/model/promote/CallbackTarget.java new file mode 100644 index 0000000..ba0e8d2 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/CallbackTarget.java @@ -0,0 +1,93 @@ +/** + * 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.model.promote; + +import java.util.Collections; +import java.util.Map; + +/** + * Created by ruhan on 12/6/18. + */ +public class CallbackTarget +{ + public enum CallbackMethod + { + POST, PUT + } + + private String url; + + private CallbackMethod method; + + private Map headers; // e.g., put( "Authorization", "Bearer ..." ) + + public CallbackTarget() + { + } + + public CallbackTarget(String url, CallbackMethod method, Map headers ) + { + this.url = url; + this.method = method; + this.headers = headers; + } + + public CallbackTarget(String url, Map headers ) + { + this( url, CallbackMethod.POST, headers ); + } + + public CallbackTarget(String url ) + { + this( url, CallbackMethod.POST, Collections.emptyMap() ); + } + + public void setUrl( String url ) + { + this.url = url; + } + + public void setMethod( CallbackMethod method ) + { + this.method = method; + } + + public void setHeaders( Map headers ) + { + this.headers = headers; + } + + public String getUrl() + { + return url; + } + + public CallbackMethod getMethod() + { + return method; + } + + public Map getHeaders() + { + return headers; + } + + @Override + public String toString() + { + return "CallbackTarget{" + "url='" + url + '\'' + ", method=" + method + '}'; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteRequest.java b/src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteRequest.java new file mode 100644 index 0000000..5cd03f1 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteRequest.java @@ -0,0 +1,169 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; +import org.commonjava.indy.client.model.StoreKey; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Configuration for promoting artifacts from one store to another (denoted by their corresponding {@link StoreKey}'s). If paths are provided, only + * promote a subset of the content in the source store, otherwise promote all. + * + * @author jdcasey + * + */ +public class PathsPromoteRequest + extends AbstractPromoteRequest +{ + + @ApiModelProperty( value="Indy store/repository key to promote FROM (formatted as: '{remote,hosted,group}:name')", required=true ) + private StoreKey source; + + @ApiModelProperty( value="Indy store/repository key to promote TO (formatted as: '{remote,hosted,group}:name')", required=true ) + private StoreKey target; + + @ApiModelProperty( "Set of paths (Strings) to promote, or ALL if no paths are specified" ) + private Set paths; + + @ApiModelProperty( "Whether to delete content from source repository once it has been promoted" ) + private boolean purgeSource; + + @ApiModelProperty( value="Run validations, verify source and target locations ONLY, do not modify anything!" ) + private boolean dryRun; + + @ApiModelProperty( value="Fire events, e.g. PromoteCompleteEvent" ) + private boolean fireEvents; + + /** + * If true, path conflict check (against concurrent promotions) is enabled and the promotion fails if pre-existent files are detected. + * For repos holding target build artifacts, we need to make sure no conflicts and no files are overridden. + * For repos like shared-imports holding download files, we don't need these checks. + */ + private boolean failWhenExists; + + public PathsPromoteRequest() + { + } + + public PathsPromoteRequest( final StoreKey source, final StoreKey target, final Set paths ) + { + this.source = source; + this.target = target; + this.paths = paths; + } + + public PathsPromoteRequest( final StoreKey source, final StoreKey target, final String... paths ) + { + this.source = source; + this.target = target; + this.paths = new HashSet<>( Arrays.asList( paths ) ); + } + + @Override + public StoreKey getSource() + { + return source; + } + + @Override + public PathsPromoteRequest setSource( final StoreKey source ) + { + this.source = source; + return this; + } + + @Override + public StoreKey getTargetKey() + { + return getTarget(); + } + + public StoreKey getTarget() + { + return target; + } + + public PathsPromoteRequest setTarget( final StoreKey target ) + { + this.target = target; + return this; + } + + public Set getPaths() + { + return paths == null ? Collections.emptySet() : paths; + } + + public PathsPromoteRequest setPaths( final Set paths ) + { + this.paths = paths; + return this; + } + + @Override + public String toString() + { + return String.format( "PathsPromoteRequest [source=%s, target=%s, paths=%s]", source, target, paths ); + } + + public PathsPromoteRequest setPurgeSource( final boolean purgeSource ) + { + this.purgeSource = purgeSource; + return this; + } + + public boolean isPurgeSource() + { + return purgeSource; + } + + public boolean isDryRun() + { + return dryRun; + } + + public PathsPromoteRequest setDryRun( final boolean dryRun ) + { + this.dryRun = dryRun; + return this; + } + + @Override + public boolean isFireEvents() + { + return fireEvents; + } + + public void setFireEvents( boolean fireEvents ) + { + this.fireEvents = fireEvents; + } + + public boolean isFailWhenExists() + { + return failWhenExists; + } + + public PathsPromoteRequest setFailWhenExists( boolean failWhenExists ) + { + this.failWhenExists = failWhenExists; + return this; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteResult.java b/src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteResult.java new file mode 100644 index 0000000..8cef6e0 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/PathsPromoteResult.java @@ -0,0 +1,124 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; +import java.util.Collections; +import java.util.Set; + +/** + * Contains the result of a promotion attempt. If the promotion is a success, the pending paths and error will be null. Otherwise, these are + * populated to support the resume feature (for transient or correctable errors). + * + * @author jdcasey + * + */ +public class PathsPromoteResult extends AbstractPromoteResult +{ + + @ApiModelProperty( "Original request (useful for resuming promotion after an error has been corrected)" ) + private PathsPromoteRequest request; + + @ApiModelProperty( value="List of paths that could NOT be promoted, in the event of an error" ) + private Set pendingPaths; + + @ApiModelProperty( "List of paths that were successfully promoted" ) + private Set completedPaths; + + @ApiModelProperty( "List of paths that were skipped (path already exists in target location)" ) + private Set skippedPaths; + + public PathsPromoteResult() + { + } + + public PathsPromoteResult( final PathsPromoteRequest request, final Set pending, final Set complete, + final Set skipped, final String error, ValidationResult validations ) + { + super( error, validations ); + this.request = request; + this.pendingPaths = pending; + this.completedPaths = complete; + this.skippedPaths = skipped; + this.error = error; + this.validations = validations; + } + + public PathsPromoteResult( final PathsPromoteRequest request, final Set pending, final Set complete, + final Set skipped, final ValidationResult validations ) + { + this( request, pending, complete, skipped, null, validations ); + } + + public PathsPromoteResult( PathsPromoteRequest request ) + { + this.request = request; + } + + public PathsPromoteResult( PathsPromoteRequest request, String error ) + { + this.request = request; + this.error = error; + } + + public Set getPendingPaths() + { + return pendingPaths == null ? Collections. emptySet() : pendingPaths; + } + + public void setPendingPaths( final Set pendingPaths ) + { + this.pendingPaths = pendingPaths; + } + + public Set getCompletedPaths() + { + return completedPaths == null ? Collections. emptySet() : completedPaths; + } + + public void setCompletedPaths( final Set completedPaths ) + { + this.completedPaths = completedPaths; + } + + public Set getSkippedPaths() + { + return skippedPaths == null ? Collections. emptySet() : skippedPaths; + } + + public void setSkippedPaths( Set skippedPaths ) + { + this.skippedPaths = skippedPaths; + } + + public PathsPromoteRequest getRequest() + { + return request; + } + + public void setRequest( final PathsPromoteRequest request ) + { + this.request = request; + } + + @Override + public String toString() + { + return String.format( "PathsPromoteResult [\n request=%s\n pendingPaths=%s\n completedPaths=%s\n skippedPaths=%s\n error=%s\n validations:\n %s\n]", + request, pendingPaths, completedPaths, skippedPaths, error, validations ); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/PromoteRequest.java b/src/main/java/org/commonjava/indy/client/model/promote/PromoteRequest.java new file mode 100644 index 0000000..42e91fd --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/PromoteRequest.java @@ -0,0 +1,40 @@ +/** + * 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.model.promote; + +import org.commonjava.indy.client.model.StoreKey; + +/** + * Created by jdcasey on 9/11/15. + */ +public interface PromoteRequest +{ + StoreKey getSource(); + + PromoteRequest setSource( StoreKey source ); + + StoreKey getTargetKey(); + + boolean isDryRun(); + + PromoteRequest setDryRun( boolean dryRun ); + + boolean isFireEvents(); + + boolean isAsync(); + + CallbackTarget getCallback(); +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/ValidationResult.java b/src/main/java/org/commonjava/indy/client/model/promote/ValidationResult.java new file mode 100644 index 0000000..b095218 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/ValidationResult.java @@ -0,0 +1,118 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by jdcasey on 9/11/15. + */ +public class ValidationResult +{ + @ApiModelProperty( value="Whether validation succeeded", required=true ) + private boolean valid = true; + + @ApiModelProperty( "Mapping of rule name to error message for any failing validations" ) + private Map validatorErrors = new HashMap<>(); + + @ApiModelProperty( "Name of validation rule-set applied" ) + private String ruleSet; + + public void addValidatorError( String validatorName, String message ) + { + valid = false; + validatorErrors.put( validatorName, message ); + } + + public boolean isValid() + { + return valid; + } + + public void setValid( boolean valid ) + { + this.valid = valid; + } + + public Map getValidatorErrors() + { + return validatorErrors; + } + + public void setValidatorErrors( Map validatorErrors ) + { + this.validatorErrors = validatorErrors; + } + + public void setRuleSet( String ruleSet ) + { + this.ruleSet = ruleSet; + } + + public String getRuleSet() + { + return ruleSet; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( !( o instanceof ValidationResult ) ) + { + return false; + } + + ValidationResult that = (ValidationResult) o; + + if ( isValid() != that.isValid() ) + { + return false; + } + if ( getValidatorErrors() != null ? + !getValidatorErrors().equals( that.getValidatorErrors() ) : + that.getValidatorErrors() != null ) + { + return false; + } + return getRuleSet() != null ? getRuleSet().equals( that.getRuleSet() ) : that.getRuleSet() == null; + + } + + @Override + public int hashCode() + { + int result = ( isValid() ? 1 : 0 ); + result = 31 * result + ( getValidatorErrors() != null ? getValidatorErrors().hashCode() : 0 ); + result = 31 * result + ( getRuleSet() != null ? getRuleSet().hashCode() : 0 ); + return result; + } + + @Override + public String toString() + { + return "ValidationResult{" + + "valid=" + valid + + ", validatorErrors=" + validatorErrors + + ", ruleSet='" + ruleSet + '\'' + + '}'; + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleDTO.java b/src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleDTO.java new file mode 100644 index 0000000..763c877 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleDTO.java @@ -0,0 +1,116 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; + +public class ValidationRuleDTO +{ + + @ApiModelProperty(value="Script name for this rule, used for reference in rule-set specifications", required=true) + private String name; + + @ApiModelProperty( value="Content of validation script", required=true ) + private String spec; + + public ValidationRuleDTO() + { + } + + public ValidationRuleDTO( final String name, final String spec ) + { + this.name = name; + this.spec = spec; + } + + public String getName() + { + return name; + } + + public void setName( final String name ) + { + this.name = name; + } + + public String getSpec() + { + return spec; + } + + public void setSpec( final String spec ) + { + this.spec = spec; + } + + @Override + public String toString() + { + return String.format( "RuleDTO [name=%s]", name ); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ( ( name == null ) ? 0 : name.hashCode() ); + result = prime * result + ( ( spec == null ) ? 0 : spec.hashCode() ); + return result; + } + + @Override + public boolean equals( final Object obj ) + { + if ( this == obj ) + { + return true; + } + if ( obj == null ) + { + return false; + } + if ( getClass() != obj.getClass() ) + { + return false; + } + final ValidationRuleDTO other = (ValidationRuleDTO) obj; + if ( name == null ) + { + if ( other.name != null ) + { + return false; + } + } + else if ( !name.equals( other.name ) ) + { + return false; + } + if ( spec == null ) + { + if ( other.spec != null ) + { + return false; + } + } + else if ( !spec.equals( other.spec ) ) + { + return false; + } + return true; + } + +} diff --git a/src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleSet.java b/src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleSet.java new file mode 100644 index 0000000..9dc3a78 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/promote/ValidationRuleSet.java @@ -0,0 +1,168 @@ +/** + * 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.model.promote; + +import io.swagger.annotations.ApiModelProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Created by jdcasey on 9/11/15. + */ +public class ValidationRuleSet +{ + @ApiModelProperty( value="Name of this validation rule-set, which will be used in validation results for promotion responses", required=true ) + private String name; + + @ApiModelProperty( value="Regular expression specifying which TARGET stores this rule-set applies to", required=true ) + private String storeKeyPattern; + + @ApiModelProperty( value="List of rule script names to execute for this rule-set (assumed to correspond to files in the promote/rules/ data directory)", required=true ) + private List ruleNames; + + @ApiModelProperty( "Key-value mapping of extra parameters that MAY be required for certain validation rules" ) + private Map validationParameters; + + private transient Pattern versionPattern; + private transient Pattern scopedVersionPattern; + + public ValidationRuleSet(){} + + public ValidationRuleSet( String name, String storeKeyPattern, List ruleNames, Map validationParameters ) + { + this.name = name; + this.storeKeyPattern = storeKeyPattern; + this.ruleNames = ruleNames; + this.validationParameters = validationParameters; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + ValidationRuleSet that = (ValidationRuleSet) o; + + if ( !getName().equals( that.getName() ) ) + { + return false; + } + if ( !getStoreKeyPattern().equals( that.getStoreKeyPattern() ) ) + { + return false; + } + return !( getRuleNames() != null ? + !getRuleNames().equals( that.getRuleNames() ) : + that.getRuleNames() != null ); + + } + + @Override + public int hashCode() + { + int result = getName().hashCode(); + result = 31 * result + getStoreKeyPattern().hashCode(); + result = 31 * result + ( getRuleNames() != null ? getRuleNames().hashCode() : 0 ); + return result; + } + + public String getName() + { + + return name; + } + + public void setName( String name ) + { + this.name = name; + } + + public String getStoreKeyPattern() + { + return storeKeyPattern; + } + + public void setStoreKeyPattern( String storeKeyPattern ) + { + this.storeKeyPattern = storeKeyPattern; + } + + public List getRuleNames() + { + return ruleNames; + } + + public void setRuleNames( List ruleNames ) + { + this.ruleNames = ruleNames; + } + + public boolean matchesKey( String keyStr ) + { + Logger logger = LoggerFactory.getLogger( getClass() ); + logger.info( "Checking whether pattern: '{}' matches store key: {}", storeKeyPattern, keyStr ); + return storeKeyPattern == null || keyStr.matches( storeKeyPattern ); + } + + public Map getValidationParameters() + { + return validationParameters; + } + + public void setValidationParameters( Map validationParameters ) + { + this.validationParameters = validationParameters; + } + + public String getValidationParameter( String key ) + { + return validationParameters == null ? null : validationParameters.get( key ); + } + + public Pattern getVersionPattern( String key ) + { + if ( versionPattern == null ) + { + versionPattern = getPropertyAsPattern ( key ); + } + return versionPattern; + } + + public Pattern getScopedVersionPattern( String key ) + { + if ( scopedVersionPattern == null ) + { + scopedVersionPattern = getPropertyAsPattern ( key ); + } + return scopedVersionPattern; + } + + private Pattern getPropertyAsPattern ( String key ) + { + String value = validationParameters.get( key ); + return value == null ? null : Pattern.compile( value ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/model/store/SimpleBooleanResultDTO.java b/src/main/java/org/commonjava/indy/client/model/store/SimpleBooleanResultDTO.java new file mode 100644 index 0000000..a7d4462 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/store/SimpleBooleanResultDTO.java @@ -0,0 +1,52 @@ +/** + * 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.model.store; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +@ApiModel( "Representation of a simple boolean result of query, like if the stores data is empty" ) +public class SimpleBooleanResultDTO +{ + @JsonProperty + @ApiModelProperty( required = true, value = "The description of this boolean result" ) + private String description; + + @JsonProperty + @ApiModelProperty( required = true, value = "The boolean result" ) + private Boolean result; + + public String getDescription() + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } + + public Boolean getResult() + { + return result; + } + + public void setResult( Boolean result ) + { + this.result = result; + } +} \ No newline at end of file diff --git a/src/main/java/org/commonjava/indy/client/model/store/StoreListingDTO.java b/src/main/java/org/commonjava/indy/client/model/store/StoreListingDTO.java new file mode 100644 index 0000000..0e682e5 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/model/store/StoreListingDTO.java @@ -0,0 +1,80 @@ +/** + * 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.model.store; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.commonjava.indy.client.model.ArtifactStore; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +@ApiModel( "List of artifact store definitions" ) +public class StoreListingDTO + implements Iterable +{ + + @ApiModelProperty( dataType = "org.commonjava.indy.model.core.ArtifactStore", required = true, value = "The store definition list" ) + private List items; + + public StoreListingDTO() + { + } + + public StoreListingDTO( final List items ) + { + this.items = items; + } + + public List getItems() + { + return items == null ? Collections.emptyList() : items; + } + + public void setItems( final List items ) + { + this.items = items; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + sb.append( "StoreListingDTO[" ); + if ( items == null || items.isEmpty() ) + { + sb.append( "NO STORES" ); + } + else + { + for ( final T item : items ) + { + sb.append( "\n " ) + .append( item ); + } + } + + sb.append( "\n]" ); + return sb.toString(); + } + + @Override + public Iterator iterator() + { + return getItems().iterator(); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyContentBrowseClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyContentBrowseClientModule.java new file mode 100644 index 0000000..0b55abb --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyContentBrowseClientModule.java @@ -0,0 +1,52 @@ +/** + * 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.modules; + +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.browse.ContentBrowseResult; +import org.commonjava.indy.client.model.StoreKey; +import org.commonjava.indy.client.model.StoreType; +import java.util.Map; + +public class IndyContentBrowseClientModule + extends IndyClientModule +{ + public ContentBrowseResult getContentList( final StoreKey key, final String path ) + throws IndyClientException + { + return http.get( + UrlUtils.buildUrl( "browse", key.getPackageType(), key.getType().singularEndpointName(), key.getName(), + path ), ContentBrowseResult.class ); + } + + public ContentBrowseResult getContentList( final String packageType, final StoreType type, final String name, + final String path ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( "browse", packageType, type.singularEndpointName(), name, path ), + ContentBrowseResult.class ); + } + + public Map headForContentList( final StoreKey key, final String path ) + throws IndyClientException + { + return http.head( + UrlUtils.buildUrl( "browse", key.getPackageType(), key.getType().singularEndpointName(), key.getName(), + path ) ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyContentClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyContentClientModule.java new file mode 100644 index 0000000..3940e18 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyContentClientModule.java @@ -0,0 +1,134 @@ +/** + * 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.modules; + +import org.apache.commons.io.IOUtils; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.helper.HttpResources; +import org.commonjava.indy.client.helper.PathInfo; +import org.commonjava.indy.client.model.StoreKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Map; + +import static org.commonjava.indy.client.util.UrlUtils.buildUrl; + +public class IndyContentClientModule + extends IndyClientModule +{ + + public static final String CHECK_CACHE_ONLY = "cache-only"; + + private static final String CONTENT_BASE = "content"; + + public String contentUrl( final StoreKey key, final String... path ) + { + return buildUrl( http.getBaseUrl(), aggregatePathParts( key, path ) ); + } + + public String contentPath( final StoreKey key, final String... path ) + { + return buildUrl( null, aggregatePathParts( key, path ) ); + } + + public void deleteCache( final StoreKey key, final String path ) // delete cached file for group/remote + throws IndyClientException + { + http.deleteCache( contentPath( key, path ) ); + } + + public void delete( final StoreKey key, final String path ) + throws IndyClientException + { + http.delete( contentPath( key, path ) ); + } + + public boolean exists( final StoreKey key, final String path ) + throws IndyClientException + { + return http.exists( contentPath( key, path ) ); + } + + public Boolean exists( StoreKey key, String path, boolean cacheOnly ) + throws IndyClientException + { + return http.exists( contentPath( key, path ), + () -> Collections.singletonMap( CHECK_CACHE_ONLY, + Boolean.toString( cacheOnly ) ) ); + } + + public void store( final StoreKey key, final String path, final InputStream stream ) + throws IndyClientException + { + http.putWithStream( contentPath( key, path ), stream ); + } + + public PathInfo getInfo( final StoreKey key, final String path ) + throws IndyClientException + { + final Map headers = http.head( contentPath( key, path ) ); + return new PathInfo( headers ); + } + + public InputStream get( final StoreKey key, final String path ) + throws IndyClientException + { + final HttpResources resources = http.getRaw( contentPath( key, path ) ); + + if ( resources.getStatusCode() != 200 ) + { + IOUtils.closeQuietly( resources ); + if ( resources.getStatusCode() == 404 ) + { + return null; + } + + throw new IndyClientException( resources.getStatusCode(), "Response returned status: %s.", + resources.getStatusLine() ); + } + + Logger logger = LoggerFactory.getLogger( getClass() ); + logger.debug( "Returning stream that should contain: {} bytes", resources.getResponse().getFirstHeader( "Content-Length" ) ); + try + { + return resources.getResponseStream(); + } + catch ( final IOException e ) + { + IOUtils.closeQuietly( resources ); + throw new IndyClientException( "Failed to open response content stream: %s", e, + e.getMessage() ); + } + } + + private String[] aggregatePathParts( final StoreKey key, final String... path ) + { + final String[] parts = new String[path.length + 4]; + int i=0; + parts[i++] = CONTENT_BASE; + parts[i++] = key.getPackageType(); + parts[i++] = key.getType().singularEndpointName(); + parts[i++] = key.getName(); + System.arraycopy( path, 0, parts, 4, path.length ); + + return parts; + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyFoloAdminClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyFoloAdminClientModule.java new file mode 100644 index 0000000..547ab2f --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyFoloAdminClientModule.java @@ -0,0 +1,141 @@ +/** + * 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.modules; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.HttpPost; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.core.IndyResponseErrorDetails; +import org.commonjava.indy.client.helper.HttpResources; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.folo.TrackedContentDTO; +import org.commonjava.indy.client.model.folo.TrackingIdsDTO; +import org.commonjava.indy.client.model.BatchDeleteRequest; +import java.io.IOException; +import java.io.InputStream; + +public class IndyFoloAdminClientModule + extends IndyClientModule +{ + + public boolean initReport( final String trackingId ) + throws IndyClientException + { + return http.put( UrlUtils.buildUrl( "/folo/admin", trackingId, "record" ), trackingId ); + } + + public InputStream getTrackingRepoZip( String trackingId ) + throws IndyClientException, IOException + { + HttpResources resources = getHttp().getRaw( UrlUtils.buildUrl("folo/admin", trackingId, "repo/zip" ) ); + if ( resources.getStatusCode() != HttpStatus.SC_OK ) + { + throw new IndyClientException( resources.getStatusCode(), "Error retrieving repository zip for tracking record: %s.\n%s", + trackingId, new IndyResponseErrorDetails( resources.getResponse() ) ); + } + + return resources.getResponseEntityContent(); + } + + public TrackedContentDTO getTrackingReport( final String trackingId ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( "/folo/admin", trackingId, "report" ), TrackedContentDTO.class ); + } + + public InputStream exportTrackingReportZip() throws IndyClientException, IOException + { + HttpResources resources = http.getRaw( UrlUtils.buildUrl( "folo/admin/report/export" ) ); + if ( resources.getStatusCode() != HttpStatus.SC_OK ) + { + throw new IndyClientException( resources.getStatusCode(), "Error retrieving record zip: %s", + new IndyResponseErrorDetails( resources.getResponse() ) ); + } + + return resources.getResponseEntityContent(); + } + + + public void importTrackingReportZip( InputStream stream ) throws IndyClientException, IOException + { + http.putWithStream( UrlUtils.buildUrl( "folo/admin/report/import" ), stream ); + } + + public TrackedContentDTO getRawTrackingContent( final String trackingId ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( "/folo/admin", trackingId, "record" ), TrackedContentDTO.class ); + } + + public TrackedContentDTO recalculateTrackingRecord( final String trackingId ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( "/folo/admin", trackingId, "record/recalculate" ), TrackedContentDTO.class ); + } + + public void clearTrackingRecord( final String trackingId ) + throws IndyClientException + { + http.delete( UrlUtils.buildUrl( "/folo/admin", trackingId, "record" ) ); + } + + public TrackingIdsDTO getTrackingIds( final String trackingType ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( "/folo/admin/report/ids", trackingType ), TrackingIdsDTO.class ); + } + + public void deleteFilesFromStoreByTrackingID( final BatchDeleteRequest request ) + throws IndyClientException + { + http.postRaw( UrlUtils.buildUrl( "/folo/admin/batch/delete" ), request ); + } + + public boolean sealTrackingRecord( String trackingId ) + throws IndyClientException + { + http.connect(); + + HttpPost request = http.newRawPost( UrlUtils.buildUrl( http.getBaseUrl(), "/folo/admin", trackingId, "record" ) ); + HttpResources resources = null; + try + { + resources = http.execute( request ); + HttpResponse response = resources.getResponse(); + StatusLine sl = response.getStatusLine(); + if ( sl.getStatusCode() != 200 ) + { + if ( sl.getStatusCode() == 404 ) + { + return false; + } + + throw new IndyClientException( sl.getStatusCode(), "Error sealing tracking record %s.\n%s", + trackingId, new IndyResponseErrorDetails( response ) ); + } + + return true; + } + finally + { + IOUtils.closeQuietly( resources ); + } + } +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyFoloContentClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyFoloContentClientModule.java new file mode 100644 index 0000000..bd32d51 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyFoloContentClientModule.java @@ -0,0 +1,119 @@ +/** + * 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.modules; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.helper.HttpResources; +import org.commonjava.indy.client.helper.PathInfo; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.StoreKey; + +public class IndyFoloContentClientModule + extends IndyClientModule +{ + + private static final String TRACKING_PATH = "/folo/track"; + + public String trackingUrl( final String id, final StoreKey key ) + { + return UrlUtils.buildUrl( http.getBaseUrl(), TRACKING_PATH, id, key.getPackageType(), + key.getType().singularEndpointName(), key.getName() ); + } + + public boolean exists( final String trackingId, final StoreKey key, final String path ) + throws IndyClientException + { + return http.exists( UrlUtils.buildUrl( TRACKING_PATH, trackingId, key.getPackageType(), + key.getType().singularEndpointName(), key.getName(), path ) ); + } + + public PathInfo store( final String trackingId, final StoreKey key, final String path, final InputStream stream ) + throws IndyClientException + { + http.putWithStream( UrlUtils.buildUrl( TRACKING_PATH, trackingId, key.getPackageType(), + key.getType().singularEndpointName(), key.getName(), path ), stream ); + + return getInfo( trackingId, key, path ); + } + + public PathInfo getInfo( final String trackingId, final StoreKey key, final String path ) + throws IndyClientException + { + final Map headers = http.head( + UrlUtils.buildUrl( TRACKING_PATH, trackingId, key.getPackageType(), + key.getType().singularEndpointName(), key.getName(), path ) ); + + return new PathInfo( headers ); + } + + public InputStream get( final String trackingId, final StoreKey key, final String path ) + throws IndyClientException + { + final HttpResources resources = http.getRaw( UrlUtils.buildUrl( TRACKING_PATH, trackingId, key.getPackageType(), + key.getType().singularEndpointName(), + key.getName(), path ) ); + + if ( resources.getStatusCode() != 200 ) + { + if ( resources.getStatusCode() == 404 ) + { + return null; + } + + IOUtils.closeQuietly( resources ); + throw new IndyClientException( "Response returned status: %s.", resources.getStatusLine() ); + } + + try + { + return resources.getResponseStream(); + } + catch ( final IOException e ) + { + throw new IndyClientException( "Failed to open response content stream: %s", e, e.getMessage() ); + } + } + + public String contentUrl( final String trackingId, final StoreKey key, final String... path ) + { + return UrlUtils.buildUrl( http.getBaseUrl(), aggregatePathParts( trackingId, key, path ) ); + } + + public String contentPath( final String trackingId, final StoreKey key, final String... path ) + { + return UrlUtils.buildUrl( null, aggregatePathParts( trackingId, key, path ) ); + } + + private String[] aggregatePathParts( final String trackingId, final StoreKey key, + final String... path ) + { + final String[] parts = new String[path.length + 4]; + int i=0; + parts[i++] = trackingId; + parts[i++] = key.getPackageType(); + parts[i++] = key.getType().singularEndpointName(); + parts[i++] = key.getName(); + System.arraycopy( path, 0, parts, 4, path.length ); + + return parts; + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyKojiClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyKojiClientModule.java new file mode 100644 index 0000000..5a52710 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyKojiClientModule.java @@ -0,0 +1,48 @@ +/** + * 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.modules; + +import org.apache.http.HttpStatus; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.model.koji.KojiRepairRequest; +import org.commonjava.indy.client.model.koji.KojiRepairResult; +import org.commonjava.indy.client.model.StoreKey; +import org.commonjava.indy.client.model.StoreType; + +public class IndyKojiClientModule + extends IndyClientModule +{ + public static final String KOJI = "koji"; + + public static final String REPAIR_KOJI = "repair/" + KOJI; + + public static final String VOL = "vol"; + + public static final String REPAIR_KOJI_VOL = REPAIR_KOJI + "/" + VOL; + + public KojiRepairResult repairVol( final String packageType, final StoreType storeType, final String storeName, + final boolean dryRun ) throws IndyClientException + { + KojiRepairRequest req = new KojiRepairRequest( new StoreKey( packageType, storeType, storeName ), dryRun ); + + KojiRepairResult result = + http.postWithResponse( REPAIR_KOJI_VOL, req, KojiRepairResult.class, HttpStatus.SC_OK ); + + return result; + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyPromoteAdminClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyPromoteAdminClientModule.java new file mode 100644 index 0000000..18e02a6 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyPromoteAdminClientModule.java @@ -0,0 +1,111 @@ +/** + * 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.modules; + +import org.apache.http.HttpStatus; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.model.StoreKey; +import org.commonjava.indy.client.model.promote.ValidationRuleDTO; +import org.commonjava.indy.client.model.promote.ValidationRuleSet; +import java.util.List; +import java.util.stream.Collectors; + +import static org.commonjava.indy.client.util.UrlUtils.buildUrl; + +public class IndyPromoteAdminClientModule + extends IndyClientModule +{ + + public static final String PROMOTE_ADMIN_BASEPATH = "admin/promotion"; + + public static final String VALIDATION_BASEPATH = PROMOTE_ADMIN_BASEPATH + "/validation"; + + public static final String VALIDATION_RELOAD_PATH = VALIDATION_BASEPATH + "/reload"; + + public static final String VALIDATION_RELOAD_RULES_PATH = VALIDATION_RELOAD_PATH + "/rules"; + + public static final String VALIDATION_RELOAD_RULESETS_PATH = VALIDATION_RELOAD_PATH + "/rulesets"; + + public static final String VALIDATION_RELOAD_ALL_PATH = VALIDATION_RELOAD_PATH + "/all"; + + public static final String VALIDATION_RULES_BASEPATH = VALIDATION_BASEPATH + "/rules"; + + public static final String VALIDATION_RULES_GET_ALL_PATH = VALIDATION_RULES_BASEPATH + "/all"; + + public static final String VALIDATION_RULES_GET_BY_NAME_PATH = VALIDATION_RULES_BASEPATH + "/named"; + + public static final String VALIDATION_RULESET_BASEPATH = VALIDATION_BASEPATH + "/rulesets"; + + public static final String VALIDATION_RULESET_GET_ALL_PATH = VALIDATION_RULESET_BASEPATH + "/all"; + + public static final String VALIDATION_RULESET_GET_BY_STOREKEY_PATH = VALIDATION_RULESET_BASEPATH + "/storekey"; + + public static final String VALIDATION_RULESET_GET_BY_NAME_PATH = VALIDATION_RULESET_BASEPATH + "/named"; + + public boolean reloadRules() + throws IndyClientException + { + return http.put( VALIDATION_RELOAD_RULES_PATH, "", HttpStatus.SC_OK ); + + } + + public boolean reloadRuleSets() + throws IndyClientException + { + return http.put( VALIDATION_RELOAD_RULESETS_PATH, "", HttpStatus.SC_OK ); + + } + + public boolean reloadRuleBundles() + throws IndyClientException + { + return http.put( VALIDATION_RELOAD_ALL_PATH, "", HttpStatus.SC_OK ); + + } + + public List getAllRules() + throws IndyClientException + { + List rules = http.get( VALIDATION_RULES_GET_ALL_PATH, List.class ); + return rules.stream().map( Object::toString ).collect( Collectors.toList() ); + } + + public ValidationRuleDTO getRuleByName( final String name ) + throws IndyClientException + { + return http.get( buildUrl( VALIDATION_RULES_GET_BY_NAME_PATH, name ), ValidationRuleDTO.class ); + } + + public List getAllRuleSets() + throws IndyClientException + { + List rules = http.get( VALIDATION_RULESET_GET_ALL_PATH, List.class ); + return rules.stream().map( Object::toString ).collect( Collectors.toList() ); + } + + public ValidationRuleSet getRuleSetByName( final String name ) + throws IndyClientException + { + return http.get( buildUrl( VALIDATION_RULESET_GET_BY_NAME_PATH, name ), ValidationRuleSet.class ); + } + + public ValidationRuleSet getRuleSetByStoreKey( final StoreKey key ) + throws IndyClientException + { + return http.get( buildUrl( VALIDATION_RULESET_GET_BY_STOREKEY_PATH, key.toString() ), ValidationRuleSet.class ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyPromoteClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyPromoteClientModule.java new file mode 100644 index 0000000..1362852 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyPromoteClientModule.java @@ -0,0 +1,83 @@ +/** + * 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.modules; + +import org.apache.http.HttpStatus; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.StoreKey; +import org.commonjava.indy.client.model.promote.PathsPromoteRequest; +import org.commonjava.indy.client.model.promote.PathsPromoteResult; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class IndyPromoteClientModule + extends IndyClientModule +{ + + public static final String PROMOTE_BASEPATH = "promotion"; + + public static final String PATHS_PROMOTE_PATH = PROMOTE_BASEPATH + "/paths/promote"; + + public static final String PATHS_ROLLBACK_PATH = PROMOTE_BASEPATH + "/paths/rollback"; + + public PathsPromoteResult promoteByPath( String trackingId, final StoreKey src, final StoreKey target, final boolean purgeSource, + final String... paths ) + throws IndyClientException + { + final PathsPromoteRequest req = + new PathsPromoteRequest( src, target, new HashSet( Arrays.asList( paths ) ) ) + .setTrackingId( trackingId ).setPurgeSource( purgeSource ); + + final PathsPromoteResult + result = http.postWithResponse( PATHS_PROMOTE_PATH, req, PathsPromoteResult.class, HttpStatus.SC_OK ); + return result; + } + + public PathsPromoteResult promoteByPath( final PathsPromoteRequest req ) + throws IndyClientException + { + final PathsPromoteResult + result = http.postWithResponse( PATHS_PROMOTE_PATH, req, PathsPromoteResult.class, HttpStatus.SC_OK ); + return result; + } + + public Set getPromotablePaths( final StoreKey storeKey ) + throws IndyClientException + { + final PathsPromoteResult result = promoteByPath( new PathsPromoteRequest( storeKey, storeKey ).setDryRun( true ) ); + return result.getPendingPaths(); + } + + public PathsPromoteResult rollbackPathPromote( final PathsPromoteResult result ) + throws IndyClientException + { + return http.postWithResponse( PATHS_ROLLBACK_PATH, result, PathsPromoteResult.class, HttpStatus.SC_OK ); + } + + public String promoteUrl() + { + return UrlUtils.buildUrl( http.getBaseUrl(), PATHS_PROMOTE_PATH ); + } + + public String rollbackUrl() + { + return UrlUtils.buildUrl( http.getBaseUrl(), PATHS_ROLLBACK_PATH ); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyRawHttpModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyRawHttpModule.java new file mode 100644 index 0000000..faf0971 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyRawHttpModule.java @@ -0,0 +1,54 @@ +/** + * 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.modules; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientHttp; +import org.commonjava.indy.client.core.IndyClientModule; + +public class IndyRawHttpModule + extends IndyClientModule +{ + + @Override + public IndyClientHttp getHttp() + { + return super.getHttp(); + } + + public CloseableHttpClient newClient() + throws IndyClientException + { + return getHttp().newClient(); + } + + public HttpClientContext newContext() + throws IndyClientException + { + return getHttp().newContext(); + } + + public void cleanup( final CloseableHttpClient client, final HttpUriRequest request, + final CloseableHttpResponse response ) + { + getHttp().cleanup( request, response, client ); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndySslValidationClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndySslValidationClientModule.java new file mode 100644 index 0000000..5f34184 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndySslValidationClientModule.java @@ -0,0 +1,55 @@ +/** + * 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.modules; + +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.ArtifactStoreValidateData; +import org.commonjava.indy.client.model.RemoteRepository; +import org.slf4j.LoggerFactory; +import java.util.HashMap; + +public class IndySslValidationClientModule + extends IndyClientModule { + + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(IndySslValidationClientModule.class); + + + // REST API paths + private static String HTTP_POST_REVALIDATE_ALL = "admin/stores/maven/remote/revalidate/all"; + private static String HTTP_POST_REVALIDATE_STORE = "admin/stores/maven/remote/"; + private static String HTTP_POST_REVALIDATE_ALL_DISABLED = "admin/stores/maven/remote/revalidate/all/disabled"; + + // Http Call Methods + + public HashMap revalidateAllStores() throws IndyClientException { + LOGGER.info("=> Sending API Call to: " + UrlUtils.buildUrl( "", HTTP_POST_REVALIDATE_ALL )); + return + http.postWithResponse( + UrlUtils.buildUrl( "", HTTP_POST_REVALIDATE_ALL), "", HashMap.class); + } + + + public ArtifactStoreValidateData revalidateStore(RemoteRepository store) throws IndyClientException { + LOGGER.info("=> Sending API Call to: " + UrlUtils.buildUrl( "", + HTTP_POST_REVALIDATE_STORE + store.getName() + "/revalidate" )); + return + http.postWithResponse( + UrlUtils.buildUrl( "", HTTP_POST_REVALIDATE_STORE + store.getName() + "/revalidate"), "",ArtifactStoreValidateData.class); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyStoreQueryClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyStoreQueryClientModule.java new file mode 100644 index 0000000..d03d99a --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyStoreQueryClientModule.java @@ -0,0 +1,249 @@ +/** + * 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.modules; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.lang3.StringUtils; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.ArtifactStore; +import org.commonjava.indy.client.model.Group; +import org.commonjava.indy.client.model.HostedRepository; +import org.commonjava.indy.client.model.RemoteRepository; +import org.commonjava.indy.client.model.StoreKey; +import org.commonjava.indy.client.model.StoreType; +import org.commonjava.indy.client.model.store.SimpleBooleanResultDTO; +import org.commonjava.indy.client.model.store.StoreListingDTO; +import org.commonjava.indy.client.util.PackageTypeConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class IndyStoreQueryClientModule + extends IndyClientModule +{ + public static final String ALL_PACKAGE_TYPES = "_all"; + + public static final String STORE_QUERY_BASEPATH = "admin/stores/query"; + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + public StoreListingDTO getAllStores( final String packageType, + final Set types, + final Boolean enabled ) + throws IndyClientException + { + final StringBuilder queryPath = new StringBuilder(); + if ( PackageTypeConstants.isValidPackageType( packageType ) ) + { + queryPath.append( "?packageType=" ).append( packageType ); + } + if ( types != null && !types.isEmpty() ) + { + if ( queryPath.length() <= 0 ) + { + queryPath.append( "?types=" ); + + } + else + { + queryPath.append( "&types=" ); + } + queryPath.append( types.stream().map( Objects::toString ).collect( Collectors.joining( "," ) ) ); + } + if ( enabled != null ) + { + if ( queryPath.length() <= 0 ) + { + queryPath.append( "?enabled=" ).append( enabled ); + + } + else + { + queryPath.append( "&enabled=" ).append( enabled ); + } + } + String path = UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "all/" + queryPath ); + return http.get( path, new TypeReference>() + { + } ); + } + + public StoreListingDTO getAllByDefaultPkgTypes() + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "byDefaultPkgTypes" ), + new TypeReference>() + { + } ); + } + + public T getByName( final String name ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "byName", name ), new TypeReference() + { + } ); + } + + public StoreListingDTO getGroupContaining( final StoreKey storeKey, final String enabled ) + throws IndyClientException + { + if ( storeKey == null ) + { + throw new IndyClientException( "StoreKey is required!" ); + } + final StringBuilder queryPath = new StringBuilder( "?storeKey=" + storeKey ); + + if ( enabled != null ) + { + queryPath.append( "&enabled=" ).append( enabled ); + } + String path = UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "groups/contains" + queryPath ); + + return http.get( path, new TypeReference>() + { + } ); + } + + public StoreListingDTO getRemoteRepositoryByUrl( final String packageType, final String url, + final String enabled ) + throws IndyClientException + { + final String pkgType = PackageTypeConstants.isValidPackageType( packageType ) ? + packageType : + PackageTypeConstants.PKG_TYPE_MAVEN; + try + { + URL u = new URL( url ); + } + catch ( MalformedURLException e ) + { + throw new IndyClientException( "url {} is not a valid!" ); + } + final StringBuilder queryPath = new StringBuilder(); + queryPath.append( "?packageType=" ).append( pkgType ).append( "&byUrl=" ).append( url ); + if ( enabled != null ) + { + queryPath.append( "&enabled=" ).append( enabled ); + } + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "remotes/" + queryPath ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO getGroupsAffectedBy( final Set storeKeys ) + throws IndyClientException + { + final String keys = storeKeys.stream().map( StoreKey::toString ).collect( Collectors.joining( "," ) ); + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "affectedBy/?keys=" + keys ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO getOrderedConcreteStoresInGroup( final String packageType, + final String groupName, + final String enabled ) + throws IndyClientException + { + return getStoresInGroup( packageType, groupName, enabled, "concretes/inGroup/" ); + } + + public StoreListingDTO getOrderedStoresInGroup( final String packageType, final String groupName, + final String enabled ) + throws IndyClientException + { + return getStoresInGroup( packageType, groupName, enabled, "inGroup/" ); + } + + private StoreListingDTO getStoresInGroup( final String packageType, final String groupName, + final String enabled, final String apiPath ) + throws IndyClientException + { + final String pkgType = PackageTypeConstants.isValidPackageType( packageType ) ? + packageType : + PackageTypeConstants.PKG_TYPE_MAVEN; + if ( StringUtils.isBlank( groupName ) ) + { + throw new IndyClientException( "group name cannot be empty!" ); + } + final String storeKey = new StoreKey( packageType, StoreType.group, groupName ).toString(); + final StringBuilder queryPath = new StringBuilder(); + queryPath.append( "?storeKey=" ).append( storeKey ); + if ( enabled != null ) + { + queryPath.append( "&enabled=" ).append( enabled ); + } + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, apiPath + queryPath ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO getAllRemoteRepositories( final String packageType, final String enabled ) + throws IndyClientException + { + return getAllSubStores( packageType, enabled, "remotes/all/" ); + } + + public StoreListingDTO getAllHostedRepositories( final String packageType, final String enabled ) + throws IndyClientException + { + return getAllSubStores( packageType, enabled, "hosteds/all/" ); + } + + public StoreListingDTO getAllGroups( final String packageType, final String enabled ) + throws IndyClientException + { + return getAllSubStores( packageType, enabled, "groups/all/" ); + } + + private StoreListingDTO getAllSubStores( final String packageType, + final String enabled, final String apiPath ) + throws IndyClientException + { + final String pkgType = PackageTypeConstants.isValidPackageType( packageType ) ? + packageType : + PackageTypeConstants.PKG_TYPE_MAVEN; + final StringBuilder queryPath = new StringBuilder(); + queryPath.append( "?packageType=" ).append( pkgType ); + if ( enabled != null ) + { + queryPath.append( "&enabled=" ).append( enabled ); + } + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, apiPath + queryPath ), + new TypeReference>() + { + } ); + } + + public SimpleBooleanResultDTO getStoreEmptyResult() + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_QUERY_BASEPATH, "isEmpty" ), + new TypeReference() + { + } ); + } + +} diff --git a/src/main/java/org/commonjava/indy/client/modules/IndyStoresClientModule.java b/src/main/java/org/commonjava/indy/client/modules/IndyStoresClientModule.java new file mode 100644 index 0000000..aa103c2 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/modules/IndyStoresClientModule.java @@ -0,0 +1,149 @@ +/** + * 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.modules; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.commonjava.indy.client.core.IndyClientException; +import org.commonjava.indy.client.core.IndyClientModule; +import org.commonjava.indy.client.util.UrlUtils; +import org.commonjava.indy.client.model.ArtifactStore; +import org.commonjava.indy.client.model.Group; +import org.commonjava.indy.client.model.HostedRepository; +import org.commonjava.indy.client.model.RemoteRepository; +import org.commonjava.indy.client.model.StoreKey; +import org.commonjava.indy.client.model.StoreType; +import org.commonjava.indy.client.model.store.StoreListingDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class IndyStoresClientModule + extends IndyClientModule +{ + public static final String ALL_PACKAGE_TYPES = "_all"; + + public static final String STORE_BASEPATH = "admin/stores"; + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + public T create( final T value, final String changelog, final Class type ) + throws IndyClientException + { + value.setMetadata( ArtifactStore.METADATA_CHANGELOG, changelog ); + return http.postWithResponse( UrlUtils.buildUrl( STORE_BASEPATH, value.getPackageType(), value.getType() + .singularEndpointName() ), + value, type ); + } + + public boolean exists( final StoreKey key ) + throws IndyClientException + { + return http.exists( UrlUtils.buildUrl( STORE_BASEPATH, key.getPackageType(), key.getType().singularEndpointName(), key.getName() ) ); + } + + public void delete( final StoreKey key, final String changelog ) + throws IndyClientException + { + http.deleteWithChangelog( UrlUtils.buildUrl( STORE_BASEPATH, key.getPackageType(), key.getType().singularEndpointName(), key.getName() ), changelog ); + } + + public void delete( final StoreKey key, final String changelog, final boolean deleteContent ) + throws IndyClientException + { + http.deleteWithChangelog( + UrlUtils.buildUrl( STORE_BASEPATH, key.getPackageType(), key.getType().singularEndpointName(), + key.getName(), deleteContent ? "?deleteContent=true" : "" ), changelog ); + } + + public boolean update( final ArtifactStore store, final String changelog ) + throws IndyClientException + { + store.setMetadata( ArtifactStore.METADATA_CHANGELOG, changelog ); + return http.put( UrlUtils.buildUrl( STORE_BASEPATH, store.getPackageType(), store.getType() + .singularEndpointName(), store.getName() ), + store ); + } + + public T load( StoreKey key, final Class cls ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, key.getPackageType(), key.getType().singularEndpointName(), key.getName() ), cls ); + } + + public StoreListingDTO listHostedRepositories() + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, ALL_PACKAGE_TYPES, StoreType.hosted.singularEndpointName() ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO listRemoteRepositories() + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, ALL_PACKAGE_TYPES, StoreType.remote.singularEndpointName() ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO listGroups() + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, ALL_PACKAGE_TYPES, StoreType.group.singularEndpointName() ), + new TypeReference>() + { + } ); + } + + + public StoreListingDTO listHostedRepositories( String packageType ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, packageType, StoreType.hosted.singularEndpointName() ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO listRemoteRepositories( String packageType ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, packageType, StoreType.remote.singularEndpointName() ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO listGroups( String packageType ) + throws IndyClientException + { + return http.get( UrlUtils.buildUrl( STORE_BASEPATH, packageType, StoreType.group.singularEndpointName() ), + new TypeReference>() + { + } ); + } + + public StoreListingDTO getRemoteByUrl( final String url, final String packageType ) + throws IndyClientException + { + return http.get( + UrlUtils.buildUrl( STORE_BASEPATH, packageType, StoreType.remote.toString(), "query" ) + "/byUrl?url=" + url, + new TypeReference>() + { + } ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/util/PackageTypeConstants.java b/src/main/java/org/commonjava/indy/client/util/PackageTypeConstants.java new file mode 100644 index 0000000..215d79f --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/util/PackageTypeConstants.java @@ -0,0 +1,34 @@ +/** + * 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.util; + +/** + * Created by ruhan on 7/24/18. + */ +public class PackageTypeConstants +{ + public static final String PKG_TYPE_MAVEN = "maven"; + + public static final String PKG_TYPE_NPM = "npm"; + + public static final String PKG_TYPE_GENERIC_HTTP = "generic-http"; + + public static boolean isValidPackageType( final String pkgType ) + { + return PKG_TYPE_MAVEN.equals( pkgType ) || PKG_TYPE_NPM.equals( pkgType ) || PKG_TYPE_GENERIC_HTTP.equals( + pkgType ); + } +} diff --git a/src/main/java/org/commonjava/indy/client/util/UrlUtils.java b/src/main/java/org/commonjava/indy/client/util/UrlUtils.java new file mode 100644 index 0000000..cff58d6 --- /dev/null +++ b/src/main/java/org/commonjava/indy/client/util/UrlUtils.java @@ -0,0 +1,195 @@ +/** + * 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.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import static org.apache.commons.lang3.StringUtils.join; + +public final class UrlUtils +{ + + private UrlUtils() + { + } + + public static String buildUrl( final String baseUrl, final String... parts ) + { + return buildUrl( baseUrl, null, parts ); + } + + public static String buildUrl( final String baseUrl, final Supplier> paramSupplier, final String... parts ) + { + Logger logger = LoggerFactory.getLogger( UrlUtils.class ); + if ( logger.isDebugEnabled() ) + { + logger.debug( "Creating url from base: '{}' and parts: {}", baseUrl, join( parts, ", " ) ); + } + + if ( parts == null || parts.length < 1 ) + { + return baseUrl; + } + + final StringBuilder urlBuilder = new StringBuilder(); + + final List list = new ArrayList<>(); + + if ( baseUrl != null && !"null".equals( baseUrl ) ) + { + list.add( baseUrl ); + } + else + { + list.add( "/" ); + } + + for ( final String part : parts ) + { + if ( part == null || "null".equals( part ) ) + { + continue; + } + + list.add( part ); + } + + urlBuilder.append( normalizePath( list.toArray( new String[list.size()] ) ) ); + // + // if ( parts[0] == null || !parts[0].startsWith( baseUrl ) ) + // { + // urlBuilder.append( baseUrl ); + // } + // + // for ( String part : parts ) + // { + // if ( part == null || part.trim() + // .length() < 1 ) + // { + // continue; + // } + // + // if ( part.startsWith( "/" ) ) + // { + // part = part.substring( 1 ); + // } + // + // if ( urlBuilder.length() > 0 && urlBuilder.charAt( urlBuilder.length() - 1 ) != '/' ) + // { + // urlBuilder.append( "/" ); + // } + // + // urlBuilder.append( part ); + // } + + if ( paramSupplier != null ) + { + Map params = paramSupplier.get(); + + urlBuilder.append( "?" ); + boolean first = true; + for ( final Map.Entry param : params.entrySet() ) + { + if ( first ) + { + first = false; + } + else + { + urlBuilder.append( "&" ); + } + + urlBuilder.append( param.getKey() ) + .append( "=" ) + .append( param.getValue() ); + } + } + + return urlBuilder.toString(); + } + + public static String normalizePath( final String... path ) + { + if ( path == null || path.length < 1 ) + { + return "/"; + } + + final StringBuilder sb = new StringBuilder(); + int idx = 0; + parts: for ( String part : path ) + { + if ( part == null || part.length() < 1 || "/".equals( part ) ) + { + continue; + } + + if ( idx == 0 && part.startsWith( "file:" ) ) + { + if ( part.length() > 5 ) + { + sb.append( part.substring( 5 ) ); + } + + continue; + } + + if ( idx > 0 ) + { + while ( part.charAt( 0 ) == '/' ) + { + if ( part.length() < 2 ) + { + continue parts; + } + + part = part.substring( 1 ); + } + } + + while ( part.charAt( part.length() - 1 ) == '/' ) + { + if ( part.length() < 2 ) + { + continue parts; + } + + part = part.substring( 0, part.length() - 1 ); + } + + if ( sb.length() > 0 ) + { + sb.append( '/' ); + } + + sb.append( part ); + idx++; + } + + if ( path[path.length - 1].endsWith( "/" ) ) + { + sb.append( "/" ); + } + + return sb.toString(); + } + +}