diff --git a/core/src/main/java/com/adobe/util/Constants.java b/core/src/main/java/com/adobe/util/Constants.java index ff9ed1e5..b5695e72 100644 --- a/core/src/main/java/com/adobe/util/Constants.java +++ b/core/src/main/java/com/adobe/util/Constants.java @@ -18,6 +18,7 @@ public class Constants { public static final String API_KEY_HEADER = "x-api-key"; public static final String IMS_URL = "https://ims-na1.adobelogin.com"; public static final String API_MANAGEMENT_URL = "https://api.adobe.io"; + public static final String CUSTOM_EVENTS_PROVIDER_METADATA_ID = "3rd_party_custom_events"; private Constants() { } diff --git a/events_mgmt/src/main/java/com/adobe/event/management/ConflictErrorDecoder.java b/events_mgmt/src/main/java/com/adobe/event/management/ConflictErrorDecoder.java new file mode 100644 index 00000000..0a6cb888 --- /dev/null +++ b/events_mgmt/src/main/java/com/adobe/event/management/ConflictErrorDecoder.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.event.management; + +import feign.FeignException; +import feign.Response; +import feign.codec.ErrorDecoder; + +public class ConflictErrorDecoder implements ErrorDecoder { + + @Override + public Exception decode(String methodKey, Response response) { + FeignException exception = FeignException.errorStatus(methodKey, response); + return (response.status()==409) ? new ConflictException(response, exception) : exception; + } +} diff --git a/events_mgmt/src/main/java/com/adobe/event/management/ConflictException.java b/events_mgmt/src/main/java/com/adobe/event/management/ConflictException.java new file mode 100644 index 00000000..5183a0d4 --- /dev/null +++ b/events_mgmt/src/main/java/com/adobe/event/management/ConflictException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.event.management; + +import static com.adobe.util.JacksonUtil.DEFAULT_OBJECT_MAPPER; + +import com.adobe.event.management.model.ErrorResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import feign.FeignException; +import feign.Response; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConflictException extends FeignException { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final String conflictingId; + + public ConflictException(Response response, FeignException exception) { + super(response.status(), exception.getMessage(), response.request(), exception); + conflictingId = getConflictingId(exception.contentUTF8()); + } + + public Optional getConflictingId(){ + return Optional.ofNullable(conflictingId); + } + + private String getConflictingId(String body){ + try { + String conflictingId = DEFAULT_OBJECT_MAPPER.readValue(body, ErrorResponse.class).getMessage(); + if (!StringUtils.isEmpty(conflictingId) && !conflictingId.contains(" ")) { + return conflictingId; + } else { + logger.warn("The Conflict/409 Error response does not hold a valid conflicting id: `{}`",conflictingId); + return null; + } + } catch (JsonProcessingException e) { + logger.warn("The Conflict/409 Error response is not of the expected format",e.getMessage()); + return null; + } + } +} diff --git a/events_mgmt/src/main/java/com/adobe/event/management/ProviderService.java b/events_mgmt/src/main/java/com/adobe/event/management/ProviderService.java index 470f70ed..8f672646 100644 --- a/events_mgmt/src/main/java/com/adobe/event/management/ProviderService.java +++ b/events_mgmt/src/main/java/com/adobe/event/management/ProviderService.java @@ -41,8 +41,30 @@ public interface ProviderService { Optional createProvider(final ProviderInputModel providerCreateModel); + /** + * + * @param providerInputModel the input payload + * @return create and if conflict/409 arises, instead, update a provider using the provided payload + */ + Optional createOrUpdateProvider(ProviderInputModel providerInputModel); + Optional updateProvider(final String id, final ProviderInputModel providerUpdateModel); + /** + * + * @param instanceId + * @return the `Custom Events` Provider associated with the provided instanceId + */ + Optional findCustomEventsProviderByInstanceId(String instanceId); + + /** + * + * @param providerMetadataId indicating the type of provider, if you are interested in + * `Custom Events`provider use findCustomEventsProviderByInstanceId + * @param instanceId + * @return the providers list matching the provided criteria and with non-empty event metadata list + * @see #findCustomEventsProviderByInstanceId(String) + */ Optional findProviderBy(final String providerMetadataId, final String instanceId); List getEventMetadata(final String providerId); diff --git a/events_mgmt/src/main/java/com/adobe/event/management/ProviderServiceImpl.java b/events_mgmt/src/main/java/com/adobe/event/management/ProviderServiceImpl.java index 6e575f2d..235e693a 100644 --- a/events_mgmt/src/main/java/com/adobe/event/management/ProviderServiceImpl.java +++ b/events_mgmt/src/main/java/com/adobe/event/management/ProviderServiceImpl.java @@ -21,15 +21,20 @@ import com.adobe.event.management.model.Provider; import com.adobe.event.management.model.ProviderCollection; import com.adobe.event.management.model.ProviderInputModel; +import com.adobe.util.Constants; import com.adobe.util.FeignUtil; import feign.RequestInterceptor; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class ProviderServiceImpl implements ProviderService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final ProviderApi providerApi; private final EventMetadataApi eventMetadataApi; private final Workspace workspace; @@ -46,6 +51,7 @@ class ProviderServiceImpl implements ProviderService { workspace.validateWorkspaceContext(); this.providerApi = FeignUtil.getDefaultBuilder() .requestInterceptor(authInterceptor) + .errorDecoder(new ConflictErrorDecoder()) .target(ProviderApi.class, apiUrl); this.eventMetadataApi = FeignUtil.getDefaultBuilder() .requestInterceptor(authInterceptor) @@ -71,23 +77,44 @@ public Optional findProviderById(final String providerId) { @Override public void deleteProvider(final String providerId) { + logger.info("Deleting provider id `{}`",providerId); providerApi.delete(workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), providerId); } @Override public Optional createProvider(final ProviderInputModel providerInputModel) { + logger.info("Creating provider using `{}`",providerInputModel); return providerApi.create(workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), providerInputModel); } + @Override + public Optional createOrUpdateProvider(final ProviderInputModel providerInputModel) { + try { + return createProvider(providerInputModel); + } + catch (ConflictException e){ + String providerId = e.getConflictingId().orElseThrow(()->e); + logger.info("Another provider (id:`{}`) exist with conflicting natural keys, " + + "trying to update it ...",providerId); + return updateProvider(providerId,providerInputModel); + } + } + @Override public Optional updateProvider(final String id, final ProviderInputModel providerUpdateModel) { + logger.info("Updating provider `{}` using `{}`",id, providerUpdateModel); return providerApi.update(workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), id, providerUpdateModel); } + @Override + public Optional findCustomEventsProviderByInstanceId(final String instanceId){ + return findProviderBy(Constants.CUSTOM_EVENTS_PROVIDER_METADATA_ID,instanceId); + } + @Override public Optional findProviderBy(final String providerMetadataId, final String instanceId) { @@ -102,7 +129,7 @@ public Optional findProviderBy(final String providerMetadataId, if (providerCollection.get().getProviders().isEmpty()) { return Optional.empty(); } else { - return Optional.of(providerCollection.get().getProviders().get(1)); + return Optional.of(providerCollection.get().getProviders().get(0)); // there can only be one by API contract } } else { @@ -129,6 +156,7 @@ public Optional getEventMetadata(final String providerId, final S @Override public Optional createEventMetadata(final String providerId, final EventMetadata eventMetadata) { + logger.info("Creating Event Metadata for provider `{}` using `{}`", providerId, eventMetadata); return eventMetadataApi.create( workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), providerId, eventMetadata); @@ -137,6 +165,7 @@ public Optional createEventMetadata(final String providerId, @Override public Optional updateEventMetadata(final String providerId, final EventMetadata eventMetadata) { + logger.info("Updating Event Metadata for provider `{}` using `{}`", providerId, eventMetadata); return eventMetadataApi.update( workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), providerId, eventMetadata.getEventCode(), eventMetadata); @@ -144,6 +173,7 @@ public Optional updateEventMetadata(final String providerId, @Override public void deleteEventMetadata(final String providerId, final String eventCode) { + logger.info("Deleting Event Metadata for provider `{}` and eventCode `{}`", providerId, eventCode); eventMetadataApi.deleteByProviderIdAndEventCode( workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), providerId, eventCode); @@ -151,6 +181,7 @@ public void deleteEventMetadata(final String providerId, final String eventCode) @Override public void deleteAllEventMetadata(final String providerId) { + logger.info("Deleting All Event Metadata for provider `{}`", providerId); eventMetadataApi.deleteByProviderId( workspace.getConsumerOrgId(), workspace.getProjectId(), workspace.getWorkspaceId(), providerId); diff --git a/events_mgmt/src/main/java/com/adobe/event/management/model/ErrorResponse.java b/events_mgmt/src/main/java/com/adobe/event/management/model/ErrorResponse.java new file mode 100644 index 00000000..3416d368 --- /dev/null +++ b/events_mgmt/src/main/java/com/adobe/event/management/model/ErrorResponse.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.event.management.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import java.util.Objects; + +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ErrorResponse { + + private String reason; + private String message; + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ErrorResponse that = (ErrorResponse) o; + return Objects.equals(reason, that.reason) && + Objects.equals(message, that.message); + } + + @Override + public int hashCode() { + return Objects.hash(reason, message); + } + + @Override + public String toString() { + return "ErrorResponse{" + + "reason='" + reason + '\'' + + ", message='" + message + '\'' + + '}'; + } +} diff --git a/events_mgmt/src/main/java/com/adobe/event/management/model/ProviderInputModel.java b/events_mgmt/src/main/java/com/adobe/event/management/model/ProviderInputModel.java index 2d2ec2c5..3d27b16d 100644 --- a/events_mgmt/src/main/java/com/adobe/event/management/model/ProviderInputModel.java +++ b/events_mgmt/src/main/java/com/adobe/event/management/model/ProviderInputModel.java @@ -12,27 +12,53 @@ package com.adobe.event.management.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; import org.apache.commons.lang3.StringUtils; /** - * The Input Model necessary to update/PUT an Adobe I/O Events Provider + * The Input Model necessary to POST/PUT an Adobe I/O Events Provider */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties( + ignoreUnknown = true +) public class ProviderInputModel { + /** + * Optional key when creating/POST-ing a new provider. + * Note it will be ignored when updating/PUT-ing it. + * If none is provided our API will create a Random UUID for you. + */ + @JsonProperty("instance_id") + private final String instanceId; + + /** + * Optional provider providerMetadataId/type when creating/POST-ing a new provider. + * Note it will be ignored when updating/PUT-ing it. + * If none is provided our API will assume you want to create a `Custom Events` Provider + * and hence will use the associated providerMetadataId. + * @see com.adobe.util.Constants#CUSTOM_EVENTS_PROVIDER_METADATA_ID + */ + @JsonProperty("provider_metadata") + private final String providerMetadataId; + /** * The label of this Events Provider, as shown on the Adobe I/O console */ - protected final String label; - protected final String description; + private final String label; + private final String description; /** * The documentation url of this Events Provider, as shown on the Adobe I/O console */ @JsonProperty("docs_url") - protected final String docsUrl; + private final String docsUrl; - protected ProviderInputModel(final String label, final String description, final String docsUrl) { + private ProviderInputModel(final String label, final String description, final String docsUrl, final String instanceId, + final String providerMetadataId) { if (StringUtils.isEmpty(label)) { throw new IllegalArgumentException( "ProviderUpdateModel is missing a label"); @@ -40,6 +66,8 @@ protected ProviderInputModel(final String label, final String description, final this.label = label; this.description = description; this.docsUrl = docsUrl; + this.providerMetadataId = providerMetadataId; + this.instanceId = instanceId; } public String getLabel() { @@ -54,6 +82,10 @@ public String getDocsUrl() { return this.docsUrl; } + public String getInstanceId() { return this.instanceId; } + + public String getProviderMetadataId() { return this.providerMetadataId; } + @Override public boolean equals(Object o) { if (this == o) { @@ -63,22 +95,24 @@ public boolean equals(Object o) { return false; } ProviderInputModel that = (ProviderInputModel) o; - return Objects.equals(label, that.label) && - Objects.equals(description, that.description) && - Objects.equals(docsUrl, that.docsUrl); + return Objects.equals(instanceId, that.instanceId) && Objects.equals(providerMetadataId, + that.providerMetadataId) && Objects.equals(label, that.label) && Objects.equals( + description, that.description) && Objects.equals(docsUrl, that.docsUrl); } @Override public int hashCode() { - return Objects.hash(label, description, docsUrl); + return Objects.hash(instanceId, providerMetadataId, label, description, docsUrl); } @Override public String toString() { - return "ProviderUpdateModel{" + + return "ProviderInputModel{" + "label='" + label + '\'' + ", description='" + description + '\'' + ", docsUrl='" + docsUrl + '\'' + + ", providerMetadataId='" + providerMetadataId + '\'' + + ", instanceId='" + instanceId + '\'' + '}'; } @@ -92,6 +126,8 @@ public static class Builder { private String label; private String description; private String docsUrl; + private String instanceId; + private String providerMetadataId; public Builder() { } @@ -111,8 +147,18 @@ public Builder docsUrl(final String docsUrl) { return this; } + public Builder providerMetadataId(final String providerMetadataId) { + this.providerMetadataId = providerMetadataId; + return this; + } + + public Builder instanceId(final String instanceId) { + this.instanceId = instanceId; + return this; + } + public ProviderInputModel build() { - return new ProviderInputModel(label, description, docsUrl); + return new ProviderInputModel(label, description, docsUrl, instanceId, providerMetadataId); } } } diff --git a/events_mgmt/src/test/java/com/adobe/event/management/ProviderServiceUpdateTestDrive.java b/events_mgmt/src/test/java/com/adobe/event/management/ProviderServiceUpdateTestDrive.java new file mode 100644 index 00000000..f2fa042c --- /dev/null +++ b/events_mgmt/src/test/java/com/adobe/event/management/ProviderServiceUpdateTestDrive.java @@ -0,0 +1,116 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.event.management; + +import com.adobe.Workspace; +import com.adobe.event.management.model.EventMetadata; +import com.adobe.event.management.model.Provider; +import com.adobe.event.management.model.ProviderInputModel; +import com.adobe.ims.JWTAuthInterceptor; +import com.adobe.ims.util.PrivateKeyBuilder; +import com.adobe.util.FileUtil; +import feign.RequestInterceptor; +import java.security.PrivateKey; +import java.util.Optional; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ProviderServiceUpdateTestDrive { + + private static final Logger logger = LoggerFactory.getLogger(ProviderServiceUpdateTestDrive.class); + + // use your own property file filePath or classpath and don't push back to git + private static final String DEFAULT_TEST_DRIVE_PROPERTIES = "workspace.secret.properties"; + private static final String API_URL = "aio_api_url"; + + /** + * use your own property file filePath or classpath. WARNING: don't push back to github as it + * contains many secrets. We do provide a sample/template workspace.properties file in the + * ./src/test/resources folder + */ + private static final String DEFAULT_TEST_PROPERTIES = "workspace.secret.properties"; + + + public static void main(String[] args) { + try { + + Properties prop = + FileUtil.readPropertiesFromClassPath( + (args != null && args.length > 0) ? args[0] : DEFAULT_TEST_DRIVE_PROPERTIES); + + PrivateKey privateKey = new PrivateKeyBuilder().properties(prop).build(); + + Workspace workspace = Workspace.builder() + .properties(prop) + .privateKey(privateKey) + .build(); + + RequestInterceptor authInterceptor = JWTAuthInterceptor.builder() + .workspace(workspace) + .build(); + + ProviderService providerService = ProviderService.builder() + .authInterceptor(authInterceptor) // [1] + .workspace(workspace) // [2] + .url(prop.getProperty(API_URL)) // you can omit this if you target prod + .build(); // + + String instanceId = "testing_update_instance_id"; + ProviderInputModel providerCreateModel = ProviderInputModel.builder() + .instanceId(instanceId) + .label("aio-lib-java test drive provider label") + .description("aio-lib-java test drive provider description") + .docsUrl( + "https://github.com/adobe/aio-lib-java/blob/main/events_mgmt/README.md#providerservice-test-drive") + .build(); + + Optional created = providerService.createOrUpdateProvider(providerCreateModel); + logger.info("created: {}", created); + String providerId = created.get().getId(); + + EventMetadata eventMetadata1 = EventMetadata.builder() + .eventCode("com.adobe.aio-java-lib.test") + .description("aio-java-lib Test Drive Event") + .build(); + logger + .info("added EventMetadata :{}", providerService.createEventMetadata(providerId, eventMetadata1)); + + + Optional found = providerService.findCustomEventsProviderByInstanceId(instanceId); + if (!found.get().getId().equals(providerId)){ + throw new RuntimeException("failing findProviderBy or createProvider API"); + } + + try { + providerService.createProvider(providerCreateModel); + } catch (ConflictException e){ + logger.info("expected Conclict {}",e.getMessage()); + } + + providerService.createOrUpdateProvider(providerCreateModel); + + Optional aboutToBeDeleted = providerService.findProviderById(created.get().getId()); + logger.info("aboutToBeDeleted: {}", aboutToBeDeleted); + + providerService.deleteProvider(aboutToBeDeleted.get().getId()); + logger.info("deleted: {}", aboutToBeDeleted.get().getId()); + logger.info("ProviderServiceUpdateTestDrive completed successfully."); + System.exit(0); + } catch (Exception e) { + logger.error(e.getMessage(), e); + System.exit(-1); + } + } + + +}