Rate this page:
The following is a simplified implementation of a PAPI client in Spring Boot intended to be used as a reference
Java version at the time of implementation: Java 17
The folder structure we have defined is as follows (most files omitted for readability):
1 2├── src │ ├── main │ │ ├── java │ │ │ ├── com.example.papiclientexamplejava │ │ │ │ ├── client │ │ │ │ ├── entities │ │ │ │ │ ├── agg │ │ │ │ │ │ ├── offering │ │ │ │ │ │ │ ├── btf │ │ │ │ │ │ │ ├── cloud │ │ │ │ │ │ │ ├── filter │ │ │ │ ├── utils │ │ │ │ ├── ExampleClass.java │ │ │ │ ├── ExampleService.java │ │ │ │ ├── PapiClientExampleJavaApplication.java │ │ ├── resources │ │ │ ├── graphql │ │ │ │ ├── queries │ │ │ │ ├── schema └── application.yml
Here are the following dependencies in pom.xml
:
1 2<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-graphql</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webflux</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webflux</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.graphql</groupId> <artifactId>spring-graphql-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webflux</artifactId> </dependency> </dependencies>
Here are the schemas that are needed to make GraphQL calls to Atlassian GraphQL Gateway (AGG). These files go
in src/main/resources/graphql/schemas
:
queries.graphqls
1 2type Query { partner: Partner }
offering.graphqls
1 2directive @oneOf on INPUT_OBJECT type Partner { "Get all cloud and BTF product offerings and pricing information" offeringCatalog: PartnerOfferingListResponse "Get offering and pricing details for a given product, including related apps" offeringDetails( where: PartnerOfferingFilter ): PartnerOfferingDetailsResponse } "Search for available product offerings" input PartnerOfferingFilter @oneOf { "Search cloud offerings by product key" cloudProduct: PartnerOfferingCloudInput "Search BTF offerings by product key" btfProduct: PartnerOfferingBtfInput } input PartnerOfferingCloudInput { "Unique identifier for a cloud product" id: ID! "Available currencies for a cloud product or app" currency: [PartnerCurrency] "Available license types for a cloud offering" pricingPlanType: [PartnerCloudLicenseType] } input PartnerOfferingBtfInput { "Unique identifier for a BTF product" productKey: ID! "Available currencies for a BTF product or app" currency: [PartnerCurrency] "Available license types for a BTF offering" licenseType: [PartnerBtfLicenseType] } interface PartnerCloudProductNode { id: ID! name: String } interface PartnerOfferingNode { id: ID! name: String } interface PartnerPricingPlanNode { id: ID! description: String currency: String type: String } interface PartnerBtfProductNode { productKey: ID! productDescription: String } interface PartnerOrderableItemNode { orderableItemId: ID! description: String licenseType: String currency: String } type PartnerOfferingDetailsResponse { cloudProducts: [PartnerCloudProduct] cloudApps: [PartnerCloudApp] btfProducts: [PartnerBtfProduct] btfApps: [PartnerBtfProduct] } type PartnerOfferingListResponse { cloudProducts: [PartnerCloudProductItem] btfProducts: [PartnerBtfProductItem] } type PartnerCloudProductItem implements PartnerCloudProductNode { id: ID! name: String } type PartnerCloudProduct implements PartnerCloudProductNode { id: ID! name: String chargeElements: [String] uncollectibleAction: PartnerUncollectibleAction offerings: [PartnerOfferingItem] } type PartnerOfferingItem implements PartnerOfferingNode { id: ID! name: String sku: String level: Int supportedBillingSystems: [String] hostingType: String pricingType: String billingType: String parent: String pricingPlans: [PartnerPricingPlan] } type PartnerCloudApp implements PartnerOfferingNode { id: ID! name: String sku: String level: Int supportedBillingSystems: [String] hostingType: String pricingType: String billingType: String parent: String } type PartnerPricingPlan implements PartnerPricingPlanNode { id: ID! description: String currency: String type: String sku: String primaryCycle: PartnerBillingCycle items: [PartnerPricingPlanItem] } type PartnerPricingPlanItem { tiersMode: String chargeElement: String chargeType: String cycle: PartnerBillingCycle tiers: [PartnerPricingTier] } type PartnerPricingTier { ceiling: Float amount: Float flatAmount: Float unitAmount: Float floor: Float policy: String } type PartnerUncollectibleAction { type: String destination: PartnerUncollectibleDestination } type PartnerUncollectibleDestination { offeringKey: ID! } type PartnerBillingCycle { name: String count: Int interval: String } type PartnerBtfProductItem implements PartnerBtfProductNode { productKey: ID! productDescription: String } type PartnerBtfProduct implements PartnerBtfProductNode { productKey: ID! productDescription: String productType: String discountOptOut: Boolean lastModified: String marketplaceAddon: Boolean contactSalesForAdditionalPricing: Boolean dataCenter: Boolean userCountEnforced: Boolean parentDescription: String parentKey: String billingType: String orderableItems: [PartnerOrderableItem] monthly: [PartnerBillingSpecificTier] annual: [PartnerBillingSpecificTier] } type PartnerOrderableItem implements PartnerOrderableItemNode { orderableItemId: ID! newPricingPlanItem: String description: String publiclyAvailable: Boolean saleType: String amount: Float renewalAmount: Float licenseType: String unitCount: Int monthsValid: Int editionDescription: String editionId: String editionType: String editionTypeIsDeprecated: Boolean unitLabel: String enterprise: Boolean starter: Boolean sku: String edition: String renewalFrequency: String currency: String } type PartnerBillingSpecificTier { unitStart: Int unitLimit: Int unitBlockSize: Int price: Float editionType: String entitionTypeIsDepercated: Boolean unitLabel: String currency: String }
common.graphqls
1 2enum PartnerCloudLicenseType { FREE COMMERCIAL ACADEMIC COMMUNITY OPEN_SOURCE EVALUATION STARTER DEVELOPER DEMONSTRATION } enum PartnerBtfLicenseType { COMMERCIAL ACADEMIC EVALUATION STARTER } enum PartnerCurrency { USD JPY }
Here are the queries that we will use to make GraphQL calls to Atlassian GraphQL Gateway (AGG). These files go in
src/main/resources/graphql/queries
:
partnerOfferingCatalogQuery.graphql
1 2query FetchOfferingCatalog { partner { offeringCatalog { btfProducts { productDescription productKey } cloudProducts { id name } } } }
partnerOfferingDetailsQuery.graphql
1 2query FetchOfferingDetails($where: PartnerOfferingFilter) { partner { offeringDetails(where: $where){ cloudProducts { id name offerings { billingType hostingType id level name pricingType sku pricingPlans { currency description items { chargeElement chargeType cycle { count interval name } tiers { amount ceiling flatAmount floor policy unitAmount } tiersMode } id primaryCycle{ count interval name } sku type } } } cloudApps { billingType hostingType id level name parent pricingType sku supportedBillingSystems } btfProducts { billingType contactSalesForAdditionalPricing dataCenter discountOptOut lastModified marketplaceAddon parentKey parentDescription productDescription productKey userCountEnforced productType monthly { currency editionType entitionTypeIsDepercated price unitBlockSize unitLabel unitLimit unitStart } orderableItems { amount currency description edition editionDescription editionId editionType editionTypeIsDeprecated enterprise licenseType monthsValid newPricingPlanItem orderableItemId publiclyAvailable renewalAmount renewalFrequency saleType sku starter unitCount unitLabel } } btfApps { billingType contactSalesForAdditionalPricing dataCenter discountOptOut lastModified marketplaceAddon parentDescription parentKey productDescription productKey productType userCountEnforced orderableItems { amount currency description edition editionDescription editionId editionType editionTypeIsDeprecated enterprise monthsValid licenseType newPricingPlanItem orderableItemId publiclyAvailable renewalAmount saleType renewalFrequency sku starter unitCount unitLabel } monthly { currency editionType entitionTypeIsDepercated price unitBlockSize unitLabel unitLimit unitStart } annual { currency editionType entitionTypeIsDepercated price unitBlockSize unitLabel unitLimit unitStart } } } } }
Here are some classes we will be needing
To make GraphQL calls to AGG we will send POST requests with this class as the body value. This file goes in src/main/java/com/example/papiclientexamplejava/entities/
:
1 2package com.example.papiclientexamplejava.entities; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(value = JsonInclude.Include.NON_NULL) public class GraphQlRequestBody { private String query; private Object variables; private String operationName; }
To read the queries inside of .graphql
files we will need this class. This file goes in src/main/java/com/example/papiclientexamplejava/utils/
:
1 2package com.example.papiclientexamplejava.utils; import java.io.IOException; public final class GraphQlSchemaReaderUtil { public static String getSchemaFromFileName(final String filename) throws IOException { return new String( GraphQlSchemaReaderUtil.class.getClassLoader().getResourceAsStream(filename).readAllBytes() ); } }
Here are the POJOs and some ancillary classes that translates the .graphqls
files into Java classes
These files go inside of src/main/java/com/example/papiclientexamplejava/entities/
:
1 2package com.example.papiclientexamplejava.entities; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(value = JsonInclude.Include.NON_NULL) public class GraphQlRequestBody { private String query; private Object variables; private String operationName; }
1 2package com.example.papiclientexamplejava.entities; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(value = JsonInclude.Include.NON_NULL) public class WhereFilter <T>{ T where; }
These files go inside of src/main/java/com/example/papiclientexamplejava/entities/offering/
:
1 2package com.example.papiclientexamplejava.entities.agg.offering; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_DEFAULT) public class PartnerData { Partner partner; }
1 2package com.example.papiclientexamplejava.entities.agg.offering; import com.example.papiclientexamplejava.entities.agg.offering.PartnerOfferingResponse; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_DEFAULT) public class Partner { PartnerOfferingResponse offeringCatalog; PartnerOfferingResponse offeringDetails; }
1 2package com.example.papiclientexamplejava.entities.agg.offering; import com.example.papiclientexamplejava.entities.agg.offering.btf.PartnerBtfProduct; import com.example.papiclientexamplejava.entities.agg.offering.cloud.PartnerCloudProduct; import com.example.papiclientexamplejava.entities.agg.offering.cloud.PartnerOfferingItem; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import java.util.List; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerOfferingResponse { List<PartnerCloudProduct> cloudProducts; List<PartnerOfferingItem> cloudApps; List<PartnerBtfProduct> btfProducts; List<PartnerBtfProduct> btfApps; }
These files go inside of src/main/java/com/example/papiclientexamplejava/entities/offering/btf/
:
1 2package com.example.papiclientexamplejava.entities.agg.offering.btf; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerBillingSpecificTier { Integer unitStart; Integer unitLimit; Integer unitBlockSize; Float price; String editionType; Boolean entitionTypeIsDepercated; String unitLabel; String currency; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.btf; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import java.util.List; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerBtfProduct { String productKey; String productDescription; String productType; Boolean discountOptOut; String lastModified; Boolean marketplaceAddon; Boolean contactSalesForAdditionalPricing; Boolean dataCenter; Boolean userCountEnforced; String parentDescription; String parentKey; String billingType; List<PartnerOrderableItem> orderableItems; List<PartnerBillingSpecificTier> monthly; List<PartnerBillingSpecificTier> annual; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.btf; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerOrderableItem { String orderableItemId; String newPricingPlanItem; String description; Boolean publiclyAvailable; String saleType; Float amount; Float renewalAmount; String licenseType; Integer unitCount; Integer monthsValid; String editionDescription; String editionId; String editionType; Boolean editionTypeIsDeprecated; String unitLabel; Boolean enterprise; Boolean starter; String sku; String edition; String renewalFrequency; String currency; }
These files go inside of src/main/java/com/example/papiclientexamplejava/entities/offering/cloud/
:
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerBillingCycle { String name; Integer count; String interval; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import java.util.List; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerCloudProduct { String id; String name; List<String> chargeElements; PartnerUncollectibleAction uncollectibleAction; List<PartnerOfferingItem> offerings; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import java.util.List; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerOfferingItem { String id; String name; String sku; Integer level; List<String> supportedBillingSystems; String hostingType; String pricingType; String billingType; String parent; List<PartnerPricingPlan> pricingPlans; //added separately by searching defaultPricingPlans }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import java.util.List; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerPricingPlan { String id; String description; String sku; String type; PartnerBillingCycle primaryCycle; String currency; List<PartnerPricingPlanItem> items; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import java.util.List; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerPricingPlanItem { String tiersMode; String chargeElement; String chargeType; PartnerBillingCycle cycle; List<PartnerPricingTier> tiers; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Builder @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerPricingTier { Float ceiling; Float amount; Float flatAmount; Float unitAmount; Float floor; String policy; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerUncollectibleAction { String type; PartnerUncollectibleDestination destination; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.cloud; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerUncollectibleDestination { String offeringKey; }
These files go inside of src/main/java/com/example/papiclientexamplejava/entities/offering/filter/
:
1 2package com.example.papiclientexamplejava.entities.agg.offering.filter; import com.example.papiclientexamplejava.entities.agg.SchemaConstants; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerOfferingBtfInput { String productKey; List<SchemaConstants.Currency> currency; List<String> orderableItemId; List<SchemaConstants.BtfLicenseType> licenseType; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.filter; import com.example.papiclientexamplejava.entities.agg.SchemaConstants; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerOfferingCloudInput { String id; List<String> offeringKey; List<String> pricingPlanKey; List<SchemaConstants.Currency> currency; List<SchemaConstants.CloudLicenseType> pricingPlanType; }
1 2package com.example.papiclientexamplejava.entities.agg.offering.filter; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder @JsonInclude(value = JsonInclude.Include.NON_NULL) public class PartnerOfferingFilter { PartnerOfferingCloudInput cloudProduct; PartnerOfferingBtfInput btfProduct; }
First, we need to set a container token value inside of application.yml
.
Next, we will create a class called WebClientBuilders
inside of src/main/java/com.example.papiclientexamplejava/client/
that will help us use Spring WebClient.
Finally, we will create a class called PapiClient
inside of src/main/java/com.example.papiclientexamplejava/client/
which uses Spring WebClient to make GraphQL calls to AGG.
application.yml
1 2container-token: "<insert container token here>"
WebClientBuilders.java
1 2package com.example.papiclientexamplejava.client; import io.netty.resolver.DefaultAddressResolverGroup; import lombok.extern.slf4j.Slf4j; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import reactor.netty.http.client.HttpClient; @Slf4j @Component public class WebClientBuilders { // returns builder with no customization public WebClient.Builder getWebClientBuilder() { return WebClient .builder() .clientConnector( new ReactorClientHttpConnector( HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE) ) ) .exchangeStrategies( ExchangeStrategies .builder() .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024) ) .build() ); } }
PapiClient.java
1 2package com.example.papiclientexamplejava.client; import com.example.papiclientexamplejava.common.PropertyConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; @Component public class PapiClient { private WebClient webClient; private String containerToken; @Autowired public PapiClient(WebClientBuilders webClientBuilders, Environment environment) { this.webClient = webClientBuilders .getWebClientBuilder() .baseUrl(environment.getProperty(PropertyConstants.SERVICES_AGG_URL)) .build(); this.containerToken = environment.getProperty("container-token"); } }
Next, let's add a method in PapiClient
that we'll use to make generic POST requests to AGG. This method will set the Authorization
header (along with any headers or generic tasks such as logging and error handling).
This method can be used by any other classes or functions to make calls to AGG. Note that the generic parameter T
will be specified by the caller.
1 2private <T> ResponseEntity<PapiResponse<T>> getPapiResponse( Class<T> clazz, GraphQlRequestBody graphQlRequestBody ) { ResponseEntity<PapiResponse<T>> papiResponse = webClient .post() .header(HttpHeaders.CONTENT_TYPE, "application/json") .header(HttpHeaders.AUTHORIZATION, "Bearer " + containerToken) .bodyValue(graphQlRequestBody) .exchangeToMono(clientResponse -> { if (clientResponse.statusCode().isError()) { return clientResponse.createException().flatMap(Mono::error); } else { ResolvableType resolvableType = ResolvableType.forClassWithGenerics( PapiResponse.class, clazz ); ParameterizedTypeReference<PapiResponse<T>> typeRef = ParameterizedTypeReference.forType( resolvableType.getType() ); return clientResponse.toEntity(typeRef); } }) .block(); return papiResponse; }
Finally, let's add methods that we'll use to make offeringCatalog
and offeringDetails
queries to AGG:
1 2public ResponseEntity<PapiResponse<PartnerData>> fetchPartnerOfferingCatalog() throws IOException { String query = GraphQlSchemaReaderUtil.getSchemaFromFileName("graphql/queries/partnerOfferingCatalogQuery.graphql"); GraphQlRequestBody graphQlRequestBody = new GraphQlRequestBody(query, null, null); return getPapiResponse( PartnerData.class, graphQlRequestBody ); } public ResponseEntity<PapiResponse<PartnerData>> fetchPartnerOfferingDetails(PartnerOfferingFilter partnerOfferingFilter) throws IOException { String query = GraphQlSchemaReaderUtil.getSchemaFromFileName("graphql/queries/partnerOfferingDetailsQuery.graphql"); WhereFilter<PartnerOfferingFilter> whereFilter = WhereFilter.<PartnerOfferingFilter>builder() .where(partnerOfferingFilter) .build(); GraphQlRequestBody graphQlRequestBody = new GraphQlRequestBody(query, whereFilter, null); return getPapiResponse( PartnerData.class, graphQlRequestBody ); }
The full PapiClient
class should look like this:
1 2package com.example.papiclientexamplejava.client; import com.example.papiclientexamplejava.common.PropertyConstants; import com.example.papiclientexamplejava.entities.GraphQlRequestBody; import com.example.papiclientexamplejava.entities.agg.PapiResponse; import com.example.papiclientexamplejava.entities.agg.offering.PartnerData; import com.example.papiclientexamplejava.entities.agg.offering.filter.PartnerOfferingFilter; import com.example.papiclientexamplejava.entities.WhereFilter; import com.example.papiclientexamplejava.utils.GraphQlSchemaReaderUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ResolvableType; import org.springframework.core.env.Environment; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.io.IOException; @Component public class PapiClient { private WebClient webClient; private String containerToken; @Autowired public PapiClient(WebClientBuilders webClientBuilders, Environment environment) { this.webClient = webClientBuilders .getWebClientBuilder() .baseUrl(environment.getProperty(PropertyConstants.SERVICES_AGG_URL)) .build(); this.containerToken = environment.getProperty("container-token"); } private <T> ResponseEntity<PapiResponse<T>> getPapiResponse( Class<T> clazz, GraphQlRequestBody graphQlRequestBody ) { ResponseEntity<PapiResponse<T>> papiResponse = webClient .post() .header(HttpHeaders.CONTENT_TYPE, "application/json") .header(HttpHeaders.AUTHORIZATION, "Bearer " + containerToken) .bodyValue(graphQlRequestBody) .exchangeToMono(clientResponse -> { if (clientResponse.statusCode().isError()) { return clientResponse.createException().flatMap(Mono::error); } else { ResolvableType resolvableType = ResolvableType.forClassWithGenerics( PapiResponse.class, clazz ); ParameterizedTypeReference<PapiResponse<T>> typeRef = ParameterizedTypeReference.forType( resolvableType.getType() ); return clientResponse.toEntity(typeRef); } }) .block(); return papiResponse; } public ResponseEntity<PapiResponse<PartnerData>> fetchPartnerOfferingCatalog() throws IOException { String query = GraphQlSchemaReaderUtil.getSchemaFromFileName("graphql/queries/partnerOfferingCatalogQuery.graphql"); GraphQlRequestBody graphQlRequestBody = new GraphQlRequestBody(query, null, null); return getPapiResponse( PartnerData.class, graphQlRequestBody ); } public ResponseEntity<PapiResponse<PartnerData>> fetchPartnerOfferingDetails(PartnerOfferingFilter partnerOfferingFilter) throws IOException { String query = GraphQlSchemaReaderUtil.getSchemaFromFileName("graphql/queries/partnerOfferingDetailsQuery.graphql"); WhereFilter<PartnerOfferingFilter> whereFilter = WhereFilter.<PartnerOfferingFilter>builder() .where(partnerOfferingFilter) .build(); GraphQlRequestBody graphQlRequestBody = new GraphQlRequestBody(query, whereFilter, null); return getPapiResponse( PartnerData.class, graphQlRequestBody ); } }
Now we can create a service called CatalogService
that uses the methods in PapiClient
to call offeringCatalog
and offeringDetails
from AGG:
1 2package com.example.papiclientexamplejava; import com.example.papiclientexamplejava.entities.agg.offering.filter.PartnerOfferingFilter; import com.example.papiclientexamplejava.client.PapiClient; import com.example.papiclientexamplejava.entities.agg.PapiResponse; import com.example.papiclientexamplejava.entities.agg.offering.PartnerData; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.io.IOException; @Service public class CatalogService { private final PapiClient papiClient; public CatalogService(PapiClient papiClient) { this.papiClient = papiClient; } public PapiResponse<PartnerData> fetchPartnerOfferingCatalog() throws IOException { ResponseEntity<PapiResponse<PartnerData>> responseEntity = papiClient.fetchPartnerOfferingCatalog(); System.out.println(responseEntity.getBody()); return responseEntity.getBody(); } public PapiResponse<PartnerData> fetchPartnerOfferingDetails(PartnerOfferingFilter partnerOfferingFilter) throws IOException { ResponseEntity<PapiResponse<PartnerData>> responseEntity = papiClient.fetchPartnerOfferingDetails(partnerOfferingFilter); System.out.println(responseEntity.getBody()); return responseEntity.getBody(); } }
We can inject CatalogService
into a class to call the offeringCatalog
query from AGG
1 2package com.example.papiclientexamplejava; import com.example.papiclientexamplejava.entities.agg.PapiResponse; import com.example.papiclientexamplejava.entities.agg.offering.PartnerData; import com.example.papiclientexamplejava.service.CatalogService; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class ExampleClass { private final CatalogService catalogService; public ExampleClass(CatalogService catalogService) { this.catalogService = catalogService; } public PapiResponse<PartnerData> fetchPartnerOfferingCatalog() throws IOException { PapiResponse<PartnerData> response = catalogService.fetchPartnerOfferingCatalog(); return response; } }
We can inject CatalogService
into a class to call the partnerOfferingDetails
query from AGG
The partnerOfferingDetails
query has a whereFilter which is required to specify either a cloud or a BTF product identifier,
along with additional optional parameters. This is defined as the PartnerOfferingFilter
GraphQL type.
We can either fetch cloud or BTF product and app details. Only one product can be fetched at a time.
Cloud products can be fetched by passing a cloud product filter
1 2package com.example.papiclientexamplejava; import com.example.papiclientexamplejava.entities.agg.PapiResponse; import com.example.papiclientexamplejava.entities.agg.offering.PartnerData; import com.example.papiclientexamplejava.entities.agg.offering.filter.PartnerOfferingCloudInput; import com.example.papiclientexamplejava.entities.agg.offering.filter.PartnerOfferingFilter; import com.example.papiclientexamplejava.service.CatalogService; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class ExampleClass { private final CatalogService catalogService; public ExampleClass(CatalogService catalogService) { this.catalogService = catalogService; } public PapiResponse<PartnerData> fetchCloudProduct() throws IOException { PartnerOfferingCloudInput partnerOfferingCloudInput = PartnerOfferingCloudInput.builder() .id("0b8ea1e2-52df-11ea-8d77-2e728ce88125") .build(); PartnerOfferingFilter partnerOfferingFilter = PartnerOfferingFilter.builder() .cloudProduct(partnerOfferingCloudInput) .build(); PapiResponse<PartnerData> response = catalogService.fetchPartnerOfferingDetails(partnerOfferingFilter); return response; } }
BTF products can be fetched by passing a BTF product filter
1 2package com.example.papiclientexamplejava; import com.example.papiclientexamplejava.entities.agg.PapiResponse; import com.example.papiclientexamplejava.entities.agg.offering.PartnerData; import com.example.papiclientexamplejava.entities.agg.offering.filter.PartnerOfferingBtfInput; import com.example.papiclientexamplejava.entities.agg.offering.filter.PartnerOfferingFilter; import com.example.papiclientexamplejava.service.CatalogService; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class ExampleClass { private final CatalogService catalogService; public ExampleClass(CatalogService catalogService) { this.catalogService = catalogService; } public PapiResponse<PartnerData> fetchBtfProduct() throws IOException { PartnerOfferingBtfInput partnerOfferingBtfInput = PartnerOfferingBtfInput.builder() .productKey("confluence") .build(); PartnerOfferingFilter partnerOfferingFilter = PartnerOfferingFilter.builder() .btfProduct(partnerOfferingBtfInput) .build(); PapiResponse<PartnerData> response = catalogService.fetchPartnerOfferingDetails(partnerOfferingFilter); return response; } }
Rate this page: