diff options
Diffstat (limited to 'dcae-analytics/dcae-analytics-web/src/main')
28 files changed, 2622 insertions, 0 deletions
diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/AnalyticsWebConfig.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/AnalyticsWebConfig.java new file mode 100644 index 0000000..a3863f5 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/AnalyticsWebConfig.java @@ -0,0 +1,33 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * @author Rajiv Singla + */ +@Configuration +@Import(DmaapMrConfig.class) +public class AnalyticsWebConfig { + + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapMrConfig.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapMrConfig.java new file mode 100644 index 0000000..97fdcc5 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapMrConfig.java @@ -0,0 +1,159 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.config; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Map; + +import org.onap.dcae.analytics.model.AnalyticsHttpConstants; +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.onap.dcae.analytics.model.DmaapMrConstants; +import org.onap.dcae.analytics.web.dmaap.MrMessageSplitter; +import org.onap.dcae.analytics.web.dmaap.MrPublisherPreferences; +import org.onap.dcae.analytics.web.dmaap.MrSubscriberPollingAdvice; +import org.onap.dcae.analytics.web.dmaap.MrSubscriberPreferences; +import org.onap.dcae.analytics.web.dmaap.MrTriggerMessageProvider; +import org.onap.dcae.analytics.web.http.HttpClientPreferencesCustomizer; +import org.onap.dcae.analytics.web.util.AnalyticsWebUtils; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpMethod; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.endpoint.MethodInvokingMessageSource; +import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; +import org.springframework.integration.http.dsl.Http; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.integration.store.BasicMessageGroupStore; +import org.springframework.integration.store.MessageGroupQueue; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.web.client.RestTemplate; + +/** + * @author Rajiv Singla + */ +@Configuration +@Import(value = {DmaapPollerConfig.class, DmaapRetryConfig.class}) +@Profile(AnalyticsProfile.DMAAP_PROFILE_NAME) +public class DmaapMrConfig { + + private static final String[] DMAAP_MAPPED_REQUEST_HEADERS = + DmaapMrConstants.DMAAP_MAPPED_HEADERS.toArray(new String[DmaapMrConstants.DMAAP_MAPPED_HEADERS.size()]); + + @Bean(name = DmaapMrConstants.DMAAP_MR_SUBSCRIBER_OUTPUT_CHANNEL_NAME) + public QueueChannel mrSubscriberOutputChannel(final BasicMessageGroupStore basicMessageGroupStore) { + return MessageChannels.queue(new MessageGroupQueue(basicMessageGroupStore, + DmaapMrConstants.DMAAP_MR_SUBSCRIBER_OUTPUT_MESSAGE_STORE_GROUP_ID)).get(); + } + + @Bean(name = DmaapMrConstants.DMAAP_MR_PUBLISHER_INPUT_CHANNEL) + public DirectChannel mrPublisherInputChannel() { + return MessageChannels.direct().get(); + } + + + @Bean + public RestTemplate mrSubscriberRestTemplate(final MrSubscriberPreferences mrSubscriberPreferences, + final RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder + .additionalCustomizers(new HttpClientPreferencesCustomizer<>(mrSubscriberPreferences)) + .build(); + } + + @Bean + public RestTemplate mrPublisherRestTemplate(final MrPublisherPreferences mrPublisherPreferences, + final RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder + .additionalCustomizers(new HttpClientPreferencesCustomizer<>(mrPublisherPreferences)) + .build(); + } + + @Bean + public MrMessageSplitter mrMessageSplitter(final ObjectMapper objectMapper, + final Integer processingBatchSize) { + final Integer batchSize = processingBatchSize != null ? processingBatchSize : + DmaapMrConstants.SUBSCRIBER_DEFAULT_PROCESSING_BATCH_SIZE; + return new MrMessageSplitter(objectMapper, batchSize); + } + + + @Bean + public MrTriggerMessageProvider mrTriggerMessageProvider( + final MrSubscriberPreferences mrSubscriberPreferences) { + return new MrTriggerMessageProvider(mrSubscriberPreferences); + } + + @Bean + public MessageSource mrMessageSource(final MrTriggerMessageProvider mrTriggerMessageProvider) { + final MethodInvokingMessageSource source = new MethodInvokingMessageSource(); + source.setObject(mrTriggerMessageProvider); + source.setMethodName(MrTriggerMessageProvider.TRIGGER_METHOD_NAME); + return source; + } + + @Bean + public IntegrationFlow mrSubscriberFlow(final PollerMetadata pollerMetadata, + final RestTemplate mrSubscriberRestTemplate, + final MessageSource mrMessageSource, + final QueueChannel mrSubscriberOutputChannel, + final MrMessageSplitter mrMessageSplitter, + final MrSubscriberPollingAdvice mrSubscriberPollingAdvice) { + return IntegrationFlows.from(mrMessageSource, c -> c.poller(pollerMetadata)) + .handle(Http.outboundGateway(m -> String.class.cast(m.getPayload()), mrSubscriberRestTemplate) + .mappedRequestHeaders(DMAAP_MAPPED_REQUEST_HEADERS) + .httpMethod(HttpMethod.GET) + .expectedResponseType(String.class), c -> c.advice(mrSubscriberPollingAdvice)) + .split(mrMessageSplitter) + .channel(mrSubscriberOutputChannel) + .get(); + } + + + @Bean + public IntegrationFlow mrPublisherFlow(final MrPublisherPreferences mrPublisherPreferences, + final RestTemplate mrPublisherRestTemplate, + final DirectChannel mrPublisherInputChannel, + final RequestHandlerRetryAdvice requestHandlerRetryAdvice) { + + return IntegrationFlows.from(mrPublisherInputChannel) + .handle(Http.outboundGateway(mrPublisherPreferences.getRequestURL(), mrPublisherRestTemplate) + .mappedRequestHeaders(DMAAP_MAPPED_REQUEST_HEADERS) + .httpMethod(HttpMethod.POST) + .extractPayload(true) + .expectedResponseType(String.class), c -> c.advice(requestHandlerRetryAdvice)) + // add end timestamp + .handle((String p, Map<String, Object> headers) -> + MessageBuilder.withPayload(p).copyHeaders(headers) + .setHeader(AnalyticsHttpConstants.REQUEST_END_TS_HEADER_KEY, + AnalyticsWebUtils.CREATION_TIMESTAMP_SUPPLIER.get()).build() + ) + .channel(DmaapMrConstants.DMAAP_MR_PUBLISHER_OUTPUT_CHANNEL) + .get(); + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapPollerConfig.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapPollerConfig.java new file mode 100644 index 0000000..ab85d5f --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapPollerConfig.java @@ -0,0 +1,79 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.config; + +import java.util.concurrent.TimeUnit; + +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.onap.dcae.analytics.web.dmaap.MrSubscriberPollingAdvice; +import org.onap.dcae.analytics.web.dmaap.MrSubscriberPollingPreferences; +import org.onap.dcae.analytics.web.dmaap.MrSubscriberPreferences; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.integration.util.DynamicPeriodicTrigger; +import org.springframework.scheduling.support.PeriodicTrigger; + +/** + * @author Rajiv Singla + */ +@Configuration +@Profile(AnalyticsProfile.DMAAP_PROFILE_NAME) +public class DmaapPollerConfig { + + @Bean + public MrSubscriberPollingAdvice mrSubscriberPollingAdvice(final DynamicPeriodicTrigger dynamicPeriodicTrigger, + final MrSubscriberPreferences mrSubscriberPreferences) { + final MrSubscriberPollingPreferences pollingPreferences = mrSubscriberPreferences.getPollingPreferences(); + final int minInterval = pollingPreferences.getMinPollingInterval(); + final int stepUpDelta = pollingPreferences.getStepUpDelta(); + final int maxInterval = pollingPreferences.getMaxPollingInterval(); + final int stepDownDelta = pollingPreferences.getStepDownDelta(); + return new MrSubscriberPollingAdvice(dynamicPeriodicTrigger, minInterval, + stepUpDelta, maxInterval, stepDownDelta); + } + + @Bean + public DynamicPeriodicTrigger dynamicPeriodicTrigger(final MrSubscriberPreferences mrSubscriberPreferences) { + final MrSubscriberPollingPreferences pollingPreferences = mrSubscriberPreferences.getPollingPreferences(); + final int minInterval = pollingPreferences.getMinPollingInterval(); + final DynamicPeriodicTrigger dynamicPeriodicTrigger = + new DynamicPeriodicTrigger(minInterval, TimeUnit.MILLISECONDS); + dynamicPeriodicTrigger.setFixedRate(true); + return dynamicPeriodicTrigger; + } + + @Bean + public PollerMetadata pollerMetadata(final DynamicPeriodicTrigger dynamicPeriodicTrigger) { + final PollerMetadata pollerMetadata = new PollerMetadata(); + pollerMetadata.setTrigger(dynamicPeriodicTrigger); + return pollerMetadata; + } + + + @Bean(name = PollerMetadata.DEFAULT_POLLER) + public PollerMetadata defaultPoller() { + PollerMetadata pollerMetadata = new PollerMetadata(); + pollerMetadata.setTrigger(new PeriodicTrigger(1000)); + return pollerMetadata; + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapRetryConfig.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapRetryConfig.java new file mode 100644 index 0000000..48f0144 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/DmaapRetryConfig.java @@ -0,0 +1,120 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.config; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.onap.dcae.analytics.model.DmaapMrConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.handler.LoggingHandler; +import org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer; +import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; +import org.springframework.integration.store.BasicMessageGroupStore; +import org.springframework.integration.store.MessageGroupQueue; +import org.springframework.messaging.MessageHandlingException; +import org.springframework.messaging.PollableChannel; +import org.springframework.retry.RetryPolicy; +import org.springframework.retry.backoff.BackOffPolicy; +import org.springframework.retry.backoff.ExponentialBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestClientException; + +/** + * @author Rajiv Singla + */ +@Configuration +@Profile(AnalyticsProfile.DMAAP_PROFILE_NAME) +@Import(MessageStoreConfig.class) +public class DmaapRetryConfig { + + @Bean + public QueueChannel errorChannel() { + return MessageChannels.queue().get(); + } + + @Bean + public IntegrationFlow loggingFlow() { + return IntegrationFlows.from(errorChannel()) + .log(LoggingHandler.Level.ERROR) + .get(); + } + + @Bean + public ErrorMessageSendingRecoverer errorMessageSendingRecoverer(final PollableChannel recoveryChannel) { + final ErrorMessageSendingRecoverer errorMessageSendingRecoverer = new ErrorMessageSendingRecoverer(); + errorMessageSendingRecoverer.setChannel(recoveryChannel); + return errorMessageSendingRecoverer; + } + + @Bean + public PollableChannel recoveryChannel(final BasicMessageGroupStore basicMessageGroupStore) { + return MessageChannels.queue(new MessageGroupQueue(basicMessageGroupStore, + DmaapMrConstants.DMAAP_MR_PUBLISHER_RECOVERY_MESSAGE_STORE_GROUP_ID)).get(); + } + + @Bean + public RequestHandlerRetryAdvice requestHandlerRetryAdvice(final RetryTemplate retryTemplate, + final ErrorMessageSendingRecoverer + errorMessageSendingRecoverer) { + final RequestHandlerRetryAdvice requestHandlerRetryAdvice = new RequestHandlerRetryAdvice(); + requestHandlerRetryAdvice.setRetryTemplate(retryTemplate); + requestHandlerRetryAdvice.setRecoveryCallback(errorMessageSendingRecoverer); + return requestHandlerRetryAdvice; + } + + @Bean + public RetryTemplate retryTemplate(final RetryPolicy retryPolicy, + final BackOffPolicy backOffPolicy) { + final RetryTemplate retryTemplate = new RetryTemplate(); + retryTemplate.setRetryPolicy(retryPolicy); + retryTemplate.setBackOffPolicy(backOffPolicy); + return retryTemplate; + } + + @Bean + public RetryPolicy retryPolicy() { + final Map<Class<? extends Throwable>, Boolean> retryableExceptions = new LinkedHashMap<>(); + retryableExceptions.put(MessageHandlingException.class, true); + retryableExceptions.put(HttpStatusCodeException.class, true); + retryableExceptions.put(RestClientException.class, true); + return new SimpleRetryPolicy(DmaapMrConstants.DEFAULT_NUM_OF_RETRIES_ON_FAILURE, retryableExceptions); + } + + @Bean + public BackOffPolicy backOffPolicy() { + final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); + backOffPolicy.setInitialInterval(DmaapMrConstants.DEFAULT_RETRY_INITIAL_INTERVAL); + backOffPolicy.setMultiplier(DmaapMrConstants.DEFAULT_RETRY_MULTIPLIER); + backOffPolicy.setMaxInterval(DmaapMrConstants.DEFAULT_RETRY_MAX_INTERVAL); + return backOffPolicy; + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/MessageStoreConfig.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/MessageStoreConfig.java new file mode 100644 index 0000000..aa1c502 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/config/MessageStoreConfig.java @@ -0,0 +1,52 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.config; + +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.integration.mongodb.store.MongoDbChannelMessageStore; +import org.springframework.integration.store.BasicMessageGroupStore; +import org.springframework.integration.store.SimpleMessageStore; + +/** + * @author Rajiv Singla + */ +@Configuration +@Profile(AnalyticsProfile.DMAAP_PROFILE_NAME) +public class MessageStoreConfig { + + @Bean + @Profile(AnalyticsProfile.NOT_MONGO_PROFILE_NAME) + public BasicMessageGroupStore simpleMessageGroupStore() { + return new SimpleMessageStore(); + } + + @Bean + @Profile(AnalyticsProfile.MONGO_PROFILE_NAME) + public BasicMessageGroupStore mongoMessageGroupStore(final MongoDbFactory mongoDbFactory) { + final MongoDbChannelMessageStore store = new MongoDbChannelMessageStore(mongoDbFactory); + store.setPriorityEnabled(true); + return store; + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrMessageSplitter.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrMessageSplitter.java new file mode 100644 index 0000000..fe8f7ed --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrMessageSplitter.java @@ -0,0 +1,188 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.dmaap; + +import static org.apache.commons.text.StringEscapeUtils.unescapeJava; +import static org.apache.commons.text.StringEscapeUtils.unescapeJson; +import static org.onap.dcae.analytics.model.AnalyticsHttpConstants.REQUEST_ID_HEADER_KEY; +import static org.onap.dcae.analytics.model.AnalyticsModelConstants.ANALYTICS_REQUEST_ID_DELIMITER; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.IntStream; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.onap.dcae.analytics.model.DmaapMrConstants; +import org.onap.dcae.analytics.web.exception.AnalyticsParsingException; +import org.onap.dcae.analytics.web.util.AnalyticsHttpUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.integration.splitter.AbstractMessageSplitter; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; + +/** + * DMaaP MR message splitter split the incoming messages into batch of given batch size + * + * @author Rajiv Singla + */ +public class MrMessageSplitter extends AbstractMessageSplitter { + + private static final Logger logger = LoggerFactory.getLogger(MrMessageSplitter.class); + + private final ObjectMapper objectMapper; + private final Integer batchSize; + + public MrMessageSplitter(@Nonnull final ObjectMapper objectMapper, + @Nonnull final Integer batchSize) { + this.objectMapper = objectMapper; + this.batchSize = batchSize; + } + + @Override + protected Object splitMessage(final Message<?> message) { + + final List<String> dmaapMessages = convertJsonToStringMessages(String.class.cast(message.getPayload()).trim()); + + final String requestId = AnalyticsHttpUtils.getRequestId(message.getHeaders()); + final String transactionId = AnalyticsHttpUtils.getTransactionId(message.getHeaders()); + + logger.info("Request Id: {}, Transaction Id: {}, Received new messages from DMaaP MR. Count: {}", + requestId, transactionId, dmaapMessages.size()); + + final List<List<String>> messagePartitions = partition(dmaapMessages, batchSize); + + logger.debug("Request Id: {}, Transaction Id: {}, Max allowed messages per batch: {}. " + + "No of batches created: {}", requestId, transactionId, batchSize, messagePartitions.size()); + + // append batch id to request id header + return messagePartitions.isEmpty() ? null : IntStream.range(0, messagePartitions.size()) + .mapToObj(batchIndex -> + MessageBuilder + .withPayload(messagePartitions.get(0)) + .copyHeaders(message.getHeaders()) + .setHeader(REQUEST_ID_HEADER_KEY, + requestId + ANALYTICS_REQUEST_ID_DELIMITER + batchIndex) + .build() + + ); + } + + /** + * Converts DMaaP MR subscriber messages json string to List of messages. If message Json String is empty + * or null + * + * @param messagesJsonString json messages String + * + * @return List containing DMaaP MR Messages + */ + private List<String> convertJsonToStringMessages(@Nullable final String messagesJsonString) { + + final LinkedList<String> messages = new LinkedList<>(); + + // If message string is not null or not empty parse json message array to List of string messages + if (messagesJsonString != null && !messagesJsonString.trim().isEmpty() + && !DmaapMrConstants.SUBSCRIBER_EMPTY_MESSAGE_RESPONSE_STRING.equals(messagesJsonString.trim())) { + + try { + // get root node + final JsonNode rootNode = objectMapper.readTree(messagesJsonString); + // iterate over root node and parse arrays messages + for (JsonNode jsonNode : rootNode) { + // if array parse it is array of messages + final String incomingMessageString = jsonNode.toString(); + if (jsonNode.isArray()) { + final List messageList = objectMapper.readValue(incomingMessageString, List.class); + for (Object message : messageList) { + final String jsonMessageString = objectMapper.writeValueAsString(message); + addUnescapedJsonToMessage(messages, jsonMessageString); + } + } else { + // parse it as object + addUnescapedJsonToMessage(messages, incomingMessageString); + } + } + + } catch (IOException e) { + final String errorMessage = String.format("Unable to convert subscriber Json String to Messages. " + + "Subscriber Response String: %s, Json Error: %s", messagesJsonString, e); + logger.error(errorMessage, e); + throw new AnalyticsParsingException(errorMessage, e); + } + + } + return messages; + } + + /** + * Adds unescaped Json messages to given messages list + * + * @param messages message list in which unescaped messages will be added + * @param incomingMessageString incoming message string that may need to be escaped + */ + private static void addUnescapedJsonToMessage(List<String> messages, String incomingMessageString) { + if (incomingMessageString.startsWith("\"") && incomingMessageString.endsWith("\"")) { + messages.add(unescapeJava(unescapeJson( + incomingMessageString.substring(1, incomingMessageString.length() - 1)))); + } else { + messages.add(unescapeJava(unescapeJson(incomingMessageString))); + } + } + + /** + * Partition list into multiple lists + * + * @param list input list that needs to be broken into chunks + * @param batchSize batch size for each list + * @param <E> element type of the list + * + * @return List containing list of entries of specified batch size + */ + private static <E> List<List<E>> partition(List<E> list, final Integer batchSize) { + + if (list == null || batchSize == null || batchSize <= 0 || list.size() < batchSize) { + return Collections.singletonList(list); + } + + final List<List<E>> result = new LinkedList<>(); + + for (int i = 0; i < list.size(); i++) { + + if (i == 0 || i % batchSize == 0) { + List<E> sublist = new LinkedList<>(); + result.add(sublist); + } + + final List<E> lastSubList = result.get(result.size() - 1); + lastSubList.add(list.get(i)); + + } + return result; + } + + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrPublisherPreferences.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrPublisherPreferences.java new file mode 100644 index 0000000..c37049d --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrPublisherPreferences.java @@ -0,0 +1,59 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.dmaap; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.net.URL; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.onap.dcae.analytics.web.http.BaseHttpClientPreferences; +import org.springframework.http.HttpHeaders; + +/** + * @author Rajiv Singla + */ +@Getter +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class MrPublisherPreferences extends BaseHttpClientPreferences { + + private static final long serialVersionUID = 1L; + + public MrPublisherPreferences(@Nonnull final String requestURL) { + super(requestURL); + } + + public MrPublisherPreferences(@Nonnull final String requestURL, + @Nullable final String httpClientId, + @Nullable final HttpHeaders httpHeaders, + @Nullable final String username, + @Nullable final String password, + @Nullable final URL proxyURL, + @Nullable final Boolean ignoreSSLValidation, + @Nullable final Boolean enableEcompAuditLogging) { + super(requestURL, httpClientId, httpHeaders, username, password, proxyURL, + ignoreSSLValidation, enableEcompAuditLogging); + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPollingAdvice.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPollingAdvice.java new file mode 100644 index 0000000..33115c5 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPollingAdvice.java @@ -0,0 +1,148 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.dmaap; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.onap.dcae.analytics.model.AnalyticsHttpConstants; +import org.onap.dcae.analytics.model.DmaapMrConstants; +import org.onap.dcae.analytics.web.util.AnalyticsHttpUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.util.DynamicPeriodicTrigger; +import org.springframework.messaging.Message; + +/** + * A polling advice which can auto adjust polling intervals depending on DMaaP MR message availability. + * Can be configured to slow down polling when messages are not available and increase polling when messages are + * indeed available. + * <p> + * The next polling interval is <b>increased</b> by given step up delta if message is <b>not found</b> up to maximum + * Polling Interval + * <br> + * The next polling interval is <b>decreased</b> by step down delta if message <b>is found</b> up to minimum + * polling interval + * + * @author Rajiv Singla + */ +public class MrSubscriberPollingAdvice extends AbstractRequestHandlerAdvice { + + private static final Logger log = LoggerFactory.getLogger(MrSubscriberPollingAdvice.class); + + private final DynamicPeriodicTrigger trigger; + private final int minPollingInterval; + private final int stepUpPollingDelta; + private final int maxPollingInterval; + private final int stepDownPollingDelta; + + private final AtomicInteger nextPollingInterval; + + /** + * Creates variable polling intervals based on message availability. + * + * @param trigger Dynamic Trigger instance + * @param minPollingInterval Minimum polling interval + * @param stepUpPollingDelta Delta by which next polling interval will be increased when message is not found + * @param maxPollingInterval Maximum polling interval + * @param stepDownPollingDelta Delta by which next polling interval will be decreased when message is found + */ + public MrSubscriberPollingAdvice(final DynamicPeriodicTrigger trigger, + final int minPollingInterval, + final int stepUpPollingDelta, + final int maxPollingInterval, + final int stepDownPollingDelta) { + this.trigger = trigger; + this.minPollingInterval = minPollingInterval; + this.stepUpPollingDelta = stepUpPollingDelta; + this.maxPollingInterval = maxPollingInterval; + this.stepDownPollingDelta = stepDownPollingDelta; + nextPollingInterval = new AtomicInteger(minPollingInterval); + } + + @Override + @SuppressWarnings("unchecked") + protected Object doInvoke(final ExecutionCallback callback, final Object target, final Message<?> message) + throws Exception { + + // execute call back + Object result = callback.execute(); + + // if result is not of type message builder just return + if (!(result instanceof MessageBuilder)) { + return result; + } + + final MessageBuilder<String> resultMessageBuilder = (MessageBuilder<String>) result; + final String payload = resultMessageBuilder.getPayload(); + final Map<String, Object> headers = resultMessageBuilder.getHeaders(); + final Object httpStatusCode = headers.get(AnalyticsHttpConstants.HTTP_STATUS_CODE_HEADER_KEY); + + // get http status code + if (httpStatusCode == null) { + return result; + } + final HttpStatus httpStatus = HttpStatus.resolve(Integer.parseInt(httpStatusCode.toString())); + + + // if status code is present and successful apply polling adjustments + if (httpStatus != null && httpStatus.is2xxSuccessful()) { + final boolean areMessagesPresent = areMessagesPresent(payload); + updateNextPollingInterval(areMessagesPresent); + + final String requestId = AnalyticsHttpUtils.getRequestId(message.getHeaders()); + final String transactionId = AnalyticsHttpUtils.getTransactionId(message.getHeaders()); + + log.debug("Request Id: {}, Transaction Id: {}, Messages Present: {}, " + + "Next Polling Interval will be: {}", requestId, transactionId, + areMessagesPresent, nextPollingInterval); + + trigger.setPeriod(nextPollingInterval.get()); + + // if no messages were found in dmaap poll - terminate further processing + if (!areMessagesPresent) { + log.info("Request Id: {}, Transaction Id: {}, No new messages found in DMaaP MR Response. " + + "No further processing required", requestId, transactionId); + return null; + } + + } + + return result; + } + + private boolean areMessagesPresent(final String payload) { + + return !(payload.isEmpty() || payload.equals(DmaapMrConstants.SUBSCRIBER_EMPTY_MESSAGE_RESPONSE_STRING)); + } + + private void updateNextPollingInterval(final boolean areMessagesPresent) { + if (areMessagesPresent) { + nextPollingInterval.getAndUpdate(interval -> interval - stepDownPollingDelta <= minPollingInterval ? + minPollingInterval : interval - stepDownPollingDelta); + } else { + nextPollingInterval.getAndUpdate(interval -> interval + stepUpPollingDelta >= maxPollingInterval ? + maxPollingInterval : interval + stepUpPollingDelta); + } + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPollingPreferences.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPollingPreferences.java new file mode 100644 index 0000000..0fe662e --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPollingPreferences.java @@ -0,0 +1,46 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.dmaap; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.io.Serializable; + +/** + * @author Rajiv Singla + */ +@Getter +@ToString +@AllArgsConstructor +@EqualsAndHashCode +public class MrSubscriberPollingPreferences implements Serializable { + + private static final long serialVersionUID = 1L; + + private final int minPollingInterval; + private final int stepUpDelta; + private final int maxPollingInterval; + private final int stepDownDelta; + + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPreferences.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPreferences.java new file mode 100644 index 0000000..0590d83 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrSubscriberPreferences.java @@ -0,0 +1,100 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.dmaap; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.net.URL; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.onap.dcae.analytics.model.DmaapMrConstants; +import org.onap.dcae.analytics.web.http.BaseHttpClientPreferences; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; + +/** + * DMaaP MR Subscriber config + * + * @author Rajiv Singla + */ +@Getter +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class MrSubscriberPreferences extends BaseHttpClientPreferences { + + private static final long serialVersionUID = 1L; + + private static final Logger logger = LoggerFactory.getLogger(MrSubscriberPreferences.class); + + private String consumerGroup; + private List<String> consumerIds; + private Integer messageLimit; + private Integer timeout; + private MrSubscriberPollingPreferences pollingPreferences; + + public MrSubscriberPreferences(@Nonnull final String requestURL) { + super(requestURL); + } + + public MrSubscriberPreferences(@Nonnull final String requestURL, + @Nullable final String httpClientId, + @Nullable final HttpHeaders httpHeaders, + @Nullable final String username, + @Nullable final String password, + @Nullable final URL proxyURL, + @Nullable final Boolean ignoreSSLValidation, + @Nullable final Boolean enableEcompAuditLogging, + @Nullable final String consumerGroup, + @Nullable final List<String> consumerIds, + @Nullable final Integer messageLimit, + @Nullable final Integer timeout, + @Nullable final MrSubscriberPollingPreferences pollingPreferences) { + super(requestURL, httpClientId, httpHeaders, username, password, proxyURL, + ignoreSSLValidation, enableEcompAuditLogging); + this.consumerGroup = consumerGroup; + this.consumerIds = consumerIds; + this.messageLimit = messageLimit; + this.timeout = timeout; + this.pollingPreferences = pollingPreferences; + } + + + public MrSubscriberPollingPreferences getPollingPreferences() { + if (pollingPreferences == null) { + logger.warn("DMaaP MR Subscriber Polling details are missing. " + + "Fixed polling rate will be used by default with polling interval: {}", + DmaapMrConstants.SUBSCRIBER_DEFAULT_FIXED_POLLING_INTERVAL); + setFixedPollingRate(DmaapMrConstants.SUBSCRIBER_DEFAULT_FIXED_POLLING_INTERVAL); + } + return pollingPreferences; + } + + private void setFixedPollingRate(final int fixedPollingInterval) { + this.pollingPreferences = + new MrSubscriberPollingPreferences(fixedPollingInterval, 0, fixedPollingInterval, 0); + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrTriggerMessageProvider.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrTriggerMessageProvider.java new file mode 100644 index 0000000..183957d --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/dmaap/MrTriggerMessageProvider.java @@ -0,0 +1,72 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.dmaap; + + +import java.net.URL; +import java.util.List; +import java.util.function.Supplier; + +import org.onap.dcae.analytics.model.AnalyticsHttpConstants; +import org.onap.dcae.analytics.model.util.supplier.UnboundedSupplier; +import org.onap.dcae.analytics.web.util.AnalyticsWebUtils; +import org.onap.dcae.analytics.web.util.function.MrSubscriberURLFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; + +/** + * Provides DMaaP MR Subscriber Trigger Message + * + * @author Rajiv Singla + */ +public class MrTriggerMessageProvider { + + private static final Logger logger = LoggerFactory.getLogger(MrTriggerMessageProvider.class); + + public static final String TRIGGER_METHOD_NAME = "getTriggerMessage"; + + private final Supplier<URL> subscriberUrlSupplier; + + public MrTriggerMessageProvider(final MrSubscriberPreferences subscriberPreferences) { + final List<URL> urls = new MrSubscriberURLFunction().apply(subscriberPreferences); + subscriberUrlSupplier = new UnboundedSupplier<>(urls.toArray(new URL[urls.size()])); + } + + /** + * DMaaP MR subscriber trigger message + * + * @return dmaap mr subscriber trigger message + */ + public Message<String> getTriggerMessage() { + final String requestId = AnalyticsWebUtils.REQUEST_ID_SUPPLIER.get(); + final String transactionId = AnalyticsWebUtils.RANDOM_ID_SUPPLIER.get(); + final String beginTimestamp = AnalyticsWebUtils.CREATION_TIMESTAMP_SUPPLIER.get(); + logger.debug("Request Id: {}. Transaction Id: {}. Begin TS: {}. Starting new DMaaP MR Subscriber poll.", + requestId, transactionId, beginTimestamp); + return MessageBuilder + .withPayload(subscriberUrlSupplier.get().toString()) + .setHeader(AnalyticsHttpConstants.REQUEST_ID_HEADER_KEY, requestId) + .setHeader(AnalyticsHttpConstants.REQUEST_TRANSACTION_ID_HEADER_KEY, transactionId) + .setHeader(AnalyticsHttpConstants.REQUEST_BEGIN_TS_HEADER_KEY, beginTimestamp) + .build(); + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/exception/AnalyticsParsingException.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/exception/AnalyticsParsingException.java new file mode 100644 index 0000000..1af40f2 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/exception/AnalyticsParsingException.java @@ -0,0 +1,33 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.exception; + +/** + * @author Rajiv Singla + */ +public class AnalyticsParsingException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public AnalyticsParsingException(final String message, final Throwable cause) { + super(message, cause); + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/exception/AnalyticsValidationException.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/exception/AnalyticsValidationException.java new file mode 100644 index 0000000..b59a2bd --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/exception/AnalyticsValidationException.java @@ -0,0 +1,32 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.exception; + +/** + * @author Rajiv Singla + */ +public class AnalyticsValidationException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public AnalyticsValidationException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/BaseHttpClientPreferences.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/BaseHttpClientPreferences.java new file mode 100644 index 0000000..3799961 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/BaseHttpClientPreferences.java @@ -0,0 +1,80 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.http; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.net.URL; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.onap.dcae.analytics.model.AnalyticsHttpConstants; +import org.onap.dcae.analytics.model.AnalyticsModelConstants; +import org.onap.dcae.analytics.model.util.supplier.RandomIdSupplier; +import org.springframework.http.HttpHeaders; + +/** + * Base implementation for {@link HttpClientPreferences} + * + * @author Rajiv Singla + */ +@Getter +@ToString(exclude = "password") +@EqualsAndHashCode +public abstract class BaseHttpClientPreferences implements HttpClientPreferences { + + protected String requestURL; + protected String httpClientId; + protected HttpHeaders requestHeaders; + protected String username; + protected String password; + protected URL proxyURL; + protected Boolean ignoreSSLValidation; + protected Boolean enableEcompAuditLogging; + + public BaseHttpClientPreferences(@Nonnull final String requestURL) { + this.requestURL = requestURL; + } + + public BaseHttpClientPreferences(@Nonnull final String requestURL, + @Nullable final String httpClientId, + @Nullable final HttpHeaders httpHeaders, + @Nullable final String username, + @Nullable final String password, + @Nullable final URL proxyURL, + @Nullable final Boolean ignoreSSLValidation, + @Nullable final Boolean enableEcompAuditLogging) { + this.requestURL = requestURL; + // create http client id if not present + this.httpClientId = httpClientId != null ? httpClientId : + AnalyticsHttpConstants.DEFAULT_HTTP_CLIENT_ID_PREFIX + + new RandomIdSupplier(AnalyticsModelConstants.DEFAULT_RANDOM_ID_LENGTH); + this.requestHeaders = httpHeaders; + this.username = username; + this.password = password; + this.proxyURL = proxyURL; + this.ignoreSSLValidation = ignoreSSLValidation != null && ignoreSSLValidation; + this.enableEcompAuditLogging = enableEcompAuditLogging != null && enableEcompAuditLogging; + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/EelfAuditLogInterceptor.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/EelfAuditLogInterceptor.java new file mode 100644 index 0000000..54b5446 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/EelfAuditLogInterceptor.java @@ -0,0 +1,206 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.http; + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Date; +import java.util.Locale; + +import org.onap.dcae.analytics.model.AnalyticsModelConstants; +import org.onap.dcae.analytics.model.TcaModelConstants; +import org.onap.dcae.analytics.model.ecomplogger.AnalyticsErrorType; +import org.onap.dcae.analytics.web.util.AnalyticsHttpUtils; +import org.onap.dcae.utils.eelf.logger.api.info.ResponseLogInfo; +import org.onap.dcae.utils.eelf.logger.api.info.ServiceLogInfo; +import org.onap.dcae.utils.eelf.logger.api.info.TargetServiceLogInfo; +import org.onap.dcae.utils.eelf.logger.api.log.EELFLogFactory; +import org.onap.dcae.utils.eelf.logger.api.log.EELFLogger; +import org.onap.dcae.utils.eelf.logger.model.info.RequestIdLogInfoImpl; +import org.onap.dcae.utils.eelf.logger.model.info.RequestTimingLogInfoImpl; +import org.onap.dcae.utils.eelf.logger.model.info.ResponseLogInfoImpl; +import org.onap.dcae.utils.eelf.logger.model.info.ServiceLogInfoImpl; +import org.onap.dcae.utils.eelf.logger.model.info.TargetServiceLogInfoImpl; +import org.onap.dcae.utils.eelf.logger.model.spec.MetricLogSpecImpl; +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.AbstractClientHttpResponse; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.StreamUtils; + +/** + * Eelf Audit log interceptor is used to log ECOMP Audit Logging + * + * @author Rajiv Singla + */ +public class EelfAuditLogInterceptor implements ClientHttpRequestInterceptor, Ordered { + + private static final EELFLogger logger = EELFLogFactory.getLogger(EelfAuditLogInterceptor.class); + + private final ServiceLogInfo serviceLogInfo; + private final String targetEntityName; + + public EelfAuditLogInterceptor(final HttpClientPreferences httpClientPreferences) { + this.serviceLogInfo = getServiceLogInfo(httpClientPreferences); + this.targetEntityName = getTargetEntityName(httpClientPreferences); + } + + @Override + public ClientHttpResponse intercept(final HttpRequest request, + final byte[] body, + final ClientHttpRequestExecution execution) throws IOException { + + final String requestId = AnalyticsHttpUtils.getRequestId(request.getHeaders()); + final String transactionId = AnalyticsHttpUtils.getTransactionId(request.getHeaders()); + + ClientHttpResponse clientHttpResponse = null; + HttpStatus httpStatus = null; + String statusText = ""; + String errorMessage = null; + final Date requestBeginTimeStamp = new Date(); + try { + clientHttpResponse = execution.execute(request, body); + httpStatus = clientHttpResponse.getStatusCode(); + if (httpStatus.is2xxSuccessful()) { + errorMessage = null; + statusText = clientHttpResponse.getStatusText(); + } else { + errorMessage = StreamUtils.copyToString(clientHttpResponse.getBody(), Charset.defaultCharset()); + statusText = clientHttpResponse.getStatusText(); + } + } catch (IOException e) { + httpStatus = HttpStatus.SERVICE_UNAVAILABLE; + statusText = AnalyticsErrorType.TIMEOUT_ERROR.getErrorDescription(); + errorMessage = e.toString(); + } + final Date requestEndTimeStamp = new Date(); + final long elapsedTime = requestEndTimeStamp.getTime() - requestBeginTimeStamp.getTime(); + final RequestTimingLogInfoImpl requestTimingLogInfo = new RequestTimingLogInfoImpl(requestBeginTimeStamp, + requestEndTimeStamp, elapsedTime); + final MetricLogSpecImpl metricLogSpec = new MetricLogSpecImpl(new RequestIdLogInfoImpl(requestId), + serviceLogInfo, requestTimingLogInfo, + getResponseLogInfo(httpStatus, statusText), getTargetServiceLogInfo(request, targetEntityName)); + + if (errorMessage != null) { + logger.metricLog().error("Request Id: {}, Transaction Id: {}, Elapsed Time: {} ms, Error Message: {} ", + metricLogSpec, requestId, transactionId, Long.toString(elapsedTime), errorMessage); + } else { + logger.metricLog().info("Request Id: {}, Transaction Id: {}, Elapsed Time: {} ms, REST Endpoint Call: {}", + metricLogSpec, requestId, transactionId, Long.toString(elapsedTime), + statusText + "-" + getTargetService(request)); + } + + return clientHttpResponse != null ? clientHttpResponse : new SimpleClientHttpResponse(); + } + + @Override + public int getOrder() { + return LOWEST_PRECEDENCE; + } + + private static ServiceLogInfo getServiceLogInfo(final HttpClientPreferences httpClientPreferences) { + return new ServiceLogInfoImpl(TcaModelConstants.TCA_SERVICE_NAME, + httpClientPreferences.getUsername(), ""); + } + + // translate well known http status code to corresponding Ecomp Logging error codes + private static ResponseLogInfo getResponseLogInfo(final HttpStatus httpStatus, final String statusText) { + if (httpStatus.is2xxSuccessful()) { + return new ResponseLogInfoImpl(AnalyticsErrorType.SUCCESSFUL.getErrorCode(), statusText); + } else if (httpStatus.is4xxClientError()) { + if (httpStatus == HttpStatus.UNAUTHORIZED || httpStatus == HttpStatus.FORBIDDEN) { + return new ResponseLogInfoImpl(AnalyticsErrorType.PERMISSION_ERROR.getErrorCode(), statusText); + } + return new ResponseLogInfoImpl(AnalyticsErrorType.DATA_ERROR.getErrorCode(), statusText); + } else if (httpStatus.is5xxServerError()) { + if (httpStatus == HttpStatus.SERVICE_UNAVAILABLE) { + return new ResponseLogInfoImpl(AnalyticsErrorType.TIMEOUT_ERROR.getErrorCode(), statusText); + } + return new ResponseLogInfoImpl(AnalyticsErrorType.BUSINESS_PROCESS_ERROR.getErrorCode(), + statusText); + } + return new ResponseLogInfoImpl(AnalyticsErrorType.UNKNOWN_ERROR.getErrorCode(), statusText); + } + + + private static TargetServiceLogInfo getTargetServiceLogInfo(final HttpRequest httpRequest, + final String targetEntityName) { + return new TargetServiceLogInfoImpl(targetEntityName, getTargetService(httpRequest), + getTargetVirtualEntity(httpRequest)); + } + + private static String getTargetVirtualEntity(final HttpRequest httpRequest) { + return httpRequest.getURI().getAuthority(); + } + + private static String getTargetService(final HttpRequest httpRequest) { + return httpRequest.getMethod() + "-" + httpRequest.getURI().getPath(); + } + + private static String getTargetEntityName(final HttpClientPreferences httpClientPreferences) { + final String simpleName = httpClientPreferences.getClass().getSimpleName().toUpperCase(Locale.getDefault()); + if (simpleName.contains("MRSUB")) { + return "DMAAP_MR_SUBSCRIBER"; + } else if (simpleName.contains("MRPUB")) { + return "DMAAP_MR_PUBLISHER"; + } else if (simpleName.contains("AAI")) { + return "AAI_ENRICHMENT"; + } else { + return "UNKNOWN"; + } + } + + + private static class SimpleClientHttpResponse extends AbstractClientHttpResponse { + @Override + public int getRawStatusCode() throws IOException { + return HttpStatus.SERVICE_UNAVAILABLE.value(); + } + + @Override + public String getStatusText() throws IOException { + return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase(); + } + + @Override + public void close() { + // do nothing + } + + @Override + public InputStream getBody() throws IOException { + return new ByteArrayInputStream("".getBytes(Charset.forName(AnalyticsModelConstants.UTF8_CHARSET_NAME))); + } + + @Override + public HttpHeaders getHeaders() { + return new HttpHeaders(); + } + } + + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/HttpClientPreferences.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/HttpClientPreferences.java new file mode 100644 index 0000000..34d0124 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/HttpClientPreferences.java @@ -0,0 +1,50 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.http; + +import java.io.Serializable; +import java.net.URL; + +import org.springframework.http.HttpHeaders; + +/** + * DCAE Analytics HTTP Client Preferences + * + * @author Rajiv Singla + */ +public interface HttpClientPreferences extends Serializable { + + String getRequestURL(); + + String getHttpClientId(); + + HttpHeaders getRequestHeaders(); + + String getUsername(); + + String getPassword(); + + URL getProxyURL(); + + Boolean getIgnoreSSLValidation(); + + Boolean getEnableEcompAuditLogging(); + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/HttpClientPreferencesCustomizer.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/HttpClientPreferencesCustomizer.java new file mode 100644 index 0000000..c5f66be --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/http/HttpClientPreferencesCustomizer.java @@ -0,0 +1,277 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.http; + + +import java.io.IOException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.DefaultProxyRoutePlanner; +import org.apache.http.ssl.SSLContextBuilder; +import org.onap.dcae.analytics.model.AnalyticsHttpConstants; +import org.onap.dcae.analytics.model.util.function.StringToURLFunction; +import org.onap.dcae.analytics.web.util.AnalyticsWebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.support.BasicAuthorizationInterceptor; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +/** + * Creates a {@link RestTemplateCustomizer} which can be used to configure the spring rest templates + * based on given {@link HttpClientPreferences} + * + * @param <T> Http Client Configurations + * + * @author Rajiv Singla + */ +public class HttpClientPreferencesCustomizer<T extends HttpClientPreferences> implements RestTemplateCustomizer { + + private static final Logger logger = LoggerFactory.getLogger(HttpClientPreferencesCustomizer.class); + + private final T httpClientConfig; + + public HttpClientPreferencesCustomizer(final T httpClientConfig) { + this.httpClientConfig = httpClientConfig; + } + + @Override + public void customize(final RestTemplate restTemplate) { + + final String httpClientId = httpClientConfig.getHttpClientId() != null ? httpClientConfig.getHttpClientId() + : AnalyticsHttpConstants.DEFAULT_HTTP_CLIENT_ID_PREFIX + AnalyticsWebUtils.RANDOM_ID_SUPPLIER.get(); + logger.debug("Customizing Rest Template for Http Client Id: {}", httpClientId); + + // set request url + final URL requestURL = new StringToURLFunction().apply(httpClientConfig.getRequestURL()) + .orElseThrow(() -> new IllegalArgumentException("Http Client URL is required")); + restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(requestURL.toString())); + + // add basic authentication headers + final String username = httpClientConfig.getUsername(); + if (username != null) { + restTemplate.getInterceptors().add( + new BasicAuthorizationInterceptor(username, httpClientConfig.getPassword())); + } + + // set default request headers + final HttpHeaders defaultRequestHeaders = httpClientConfig.getRequestHeaders(); + if (defaultRequestHeaders != null) { + restTemplate.getInterceptors().add(new DefaultHeadersRequestInterceptor(defaultRequestHeaders)); + } + + // create new http client builder + final HttpClientBuilder httpClientBuilder = HttpClients.custom().useSystemProperties() + .disableContentCompression(); + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + + // set basic authentication credentials + configureAuthenticationCredentials(httpClientId, requestURL, credentialsProvider); + // set up proxy url + configureProxySettings(httpClientId, httpClientBuilder, credentialsProvider); + // setup credentials provider + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + // set up ssl Context + configureSSLContext(httpClientId, httpClientBuilder); + + // set rest client builder + final HttpComponentsClientHttpRequestFactory httpRequestFactory = + new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build()); + + // set ecomp logging interceptor + if (httpClientConfig.getEnableEcompAuditLogging()) { + restTemplate.getInterceptors().add(new EelfAuditLogInterceptor(httpClientConfig)); + } + + restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory)); + } + + /** + * Configures authentication credentials + * + * @param httpClientId http client id + * @param requestURL request url + * @param credentialsProvider credentials provider + */ + private void configureAuthenticationCredentials(final String httpClientId, final URL requestURL, + final CredentialsProvider credentialsProvider) { + final String username = httpClientConfig.getUsername(); + if (username != null) { + logger.info("Setting basic Authentication credentials for Http Client Id: {} with username: {}", + httpClientId, username); + final String requestURLProtocol = requestURL.getProtocol(); + final String requestUrlHost = requestURL.getHost(); + final Integer requestUrlPortNumber = requestURL.getPort(); + final HttpHost requestURLHost = new HttpHost(requestUrlHost, requestUrlPortNumber, requestURLProtocol); + final String password = httpClientConfig.getPassword(); + final AuthScope httpClientAuthScope = new AuthScope(requestURLHost); + final Credentials credentials = new UsernamePasswordCredentials(username, password); + credentialsProvider.setCredentials(httpClientAuthScope, credentials); + } else { + logger.warn("No credentials set for Http Client Id: {}. No username present", httpClientId); + } + } + + /** + * Configures proxy host, port and authentication + * + * @param httpClientId http client id + * @param httpClientBuilder http client builder + * @param credentialsProvider http credentials provider + */ + private void configureProxySettings(final String httpClientId, final HttpClientBuilder httpClientBuilder, + final CredentialsProvider credentialsProvider) { + + final URL proxyURL = httpClientConfig.getProxyURL(); + + if (proxyURL == null) { + logger.debug("Proxy not Enabled - bypassing setting Proxy settings for Http Client Id: {}", httpClientId); + return; + } + + final String proxyProtocol = proxyURL.getProtocol(); + final String proxyHost = proxyURL.getHost(); + final Integer proxyPort = proxyURL.getPort(); + final HttpHost proxy = new HttpHost(proxyHost, proxyPort, proxyProtocol); + + logger.info("Setting up proxy for Http Client Id: {} as: {}", httpClientId, proxy); + + final DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); + httpClientBuilder.setRoutePlanner(routePlanner); + + // get proxy credentials information + final String userInfo = proxyURL.getUserInfo(); + + if (!StringUtils.hasText(userInfo)) { + logger.debug("Proxy username not present. " + + "No proxy authentication credentials will be set for Http Client Id: {}", httpClientId); + return; + } + + final String[] userInfoArray = userInfo.split(":"); + final String proxyUsername = userInfoArray[0]; + String proxyPassword = null; + if (userInfoArray.length > 1) { + proxyPassword = userInfoArray[1]; + } + logger.info("Setting proxy credentials with username: {} for Http Client Id: {}", proxyUsername, httpClientId); + final AuthScope proxyAuthScope = new AuthScope(proxyHost, proxyPort); + final Credentials proxyCredentials = new UsernamePasswordCredentials(proxyUsername, proxyPassword); + credentialsProvider.setCredentials(proxyAuthScope, proxyCredentials); + } + + /** + * Configures SSL Context + * + * @param httpClientId http client id + * @param httpClientBuilder http client builder + */ + private void configureSSLContext(final String httpClientId, final HttpClientBuilder httpClientBuilder) { + + // Setup SSL Context to ignore SSL certificate issues if ignoreSSLCertificateErrors is true + final boolean ignoreSSLValidation = + Optional.ofNullable(httpClientConfig.getIgnoreSSLValidation()).orElse(false); + logger.info("Ignore SSL Certificate Errors attributed is set to: {} for Http Client Id: {}", + ignoreSSLValidation, httpClientId); + + if (!ignoreSSLValidation) { + logger.info("SSL Validation will be enforced for Http Client Id: {}", httpClientId); + return; + } + + logger.warn("SSL Certificate Errors will be ignored for Http Client Id: {}", httpClientId); + try { + SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); + sslContextBuilder.loadTrustMaterial(null, new AlwaysTrustingTrustStrategy()); + httpClientBuilder.setSSLContext(sslContextBuilder.build()); + } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + ReflectionUtils.rethrowRuntimeException(e); + } + httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); + + } + + + /** + * Header Request Interceptor adds defaults headers if not set explicitly + */ + private static class DefaultHeadersRequestInterceptor implements ClientHttpRequestInterceptor { + private final HttpHeaders httpHeaders; + + DefaultHeadersRequestInterceptor(final HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + } + + @Override + public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, + final ClientHttpRequestExecution execution) throws IOException { + final HttpHeaders currentRequestHeaders = request.getHeaders(); + for (Map.Entry<String, List<String>> defaultHttpHeader : httpHeaders.entrySet()) { + if (!currentRequestHeaders.containsKey(defaultHttpHeader.getKey())) { + currentRequestHeaders.addAll(defaultHttpHeader.getKey(), defaultHttpHeader.getValue()); + } + } + currentRequestHeaders.setAccept(httpHeaders.getAccept()); + currentRequestHeaders.setAcceptCharset(httpHeaders.getAcceptCharset()); + currentRequestHeaders.remove(HttpHeaders.ACCEPT_ENCODING); + return execution.execute(request, body); + } + } + + /** + * An implementation of SSL Trust Strategy which does no SSL certificate validation effectively + * bypassing any SSL certificate related issues + */ + private static class AlwaysTrustingTrustStrategy implements TrustStrategy { + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + return true; + } + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/mongo/MongoProfileCondition.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/mongo/MongoProfileCondition.java new file mode 100644 index 0000000..d984085 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/mongo/MongoProfileCondition.java @@ -0,0 +1,54 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.mongo; + +import java.util.Arrays; + +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Condition that configures mongo support only if mongo profile is present + * + * @author Rajiv Singla + */ +public class MongoProfileCondition implements Condition { + + private static final Logger logger = LoggerFactory.getLogger(MongoProfileCondition.class); + + @Override + public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) { + + final boolean isMongoProfileActive = Arrays.stream(context.getEnvironment().getActiveProfiles()) + .anyMatch(activeProfile -> activeProfile.equalsIgnoreCase(AnalyticsProfile.MONGO_PROFILE_NAME)); + + if (isMongoProfileActive) { + logger.info("Mongo Profile is Active. Mongo support is enabled"); + return true; + } + + logger.info("Mongo Profile is NOT Active. Mongo support is disabled"); + return false; + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/spring/ConfigBindingServiceEnvironmentPostProcessor.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/spring/ConfigBindingServiceEnvironmentPostProcessor.java new file mode 100644 index 0000000..2073127 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/spring/ConfigBindingServiceEnvironmentPostProcessor.java @@ -0,0 +1,148 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.spring; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.onap.dcae.analytics.model.configbindingservice.ConfigBindingServiceConstants; +import org.onap.dcae.analytics.model.util.function.JsonStringToMapFunction; +import org.onap.dcae.analytics.model.util.supplier.ConfigBindingServiceJsonSupplier; +import org.onap.dcae.analytics.web.exception.AnalyticsValidationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.util.ClassUtils; +import org.springframework.web.context.support.StandardServletEnvironment; + +/** + * A custom spring framework environment post processor which can fetch and populate spring context with + * Config Binding Service application properties. + * <p> + * Activated only when config binding service profile is active. + * + * @author Rajiv Singla + */ +public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(ConfigBindingServiceEnvironmentPostProcessor.class); + private static final String SERVLET_ENVIRONMENT_CLASS = + "org.springframework.web.context.support.StandardServletEnvironment"; + + private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE; + + @Override + public void postProcessEnvironment(final ConfigurableEnvironment environment, final SpringApplication application) { + + final boolean isConfigServiceProfilePresent = Arrays.stream(environment.getActiveProfiles()) + .anyMatch(p -> p.equalsIgnoreCase(AnalyticsProfile.CONFIG_BINDING_SERVICE_PROFILE_NAME)); + + if (!isConfigServiceProfilePresent) { + logger.info("Config Binding Service Profile is not active. " + + "Skipping Adding config binding service properties"); + return; + } + + logger.info("Config Binding Service Profile is active. " + + "Application properties will be fetched from config binding service"); + + // Fetch config binding service json + final Optional<String> configServiceJsonOptional = new ConfigBindingServiceJsonSupplier().get(); + + if (!configServiceJsonOptional.isPresent()) { + final String errorMessage = "Unable to get fetch application configuration from config binding service"; + throw new AnalyticsValidationException(errorMessage, new IllegalStateException(errorMessage)); + } + + final String configServicePropertiesKey = ConfigBindingServiceConstants.CONFIG_BINDING_SERVICE_PROPERTIES_KEY; + + // convert fetch config binding service json string to Map of property key and values + final Map<String, Object> configPropertiesMap = configServiceJsonOptional + .map(new JsonStringToMapFunction(configServicePropertiesKey)) + .orElse(Collections.emptyMap()); + + if (configPropertiesMap.isEmpty()) { + + logger.warn("No properties found in config binding service"); + + } else { + + // remove config service key prefix on spring reserved property key prefixes + final Set<String> springKeyPrefixes = ConfigBindingServiceConstants.SPRING_RESERVED_PROPERTIES_KEY_PREFIXES; + final Set<String> springKeys = springKeyPrefixes.stream() + .map(springKeyPrefix -> configServicePropertiesKey + "." + springKeyPrefix) + .collect(Collectors.toSet()); + + final Map<String, Object> filterKeyMap = configPropertiesMap.entrySet() + .stream() + .collect(Collectors.toMap( + (Map.Entry<String, Object> e) -> + springKeys.stream().anyMatch(springKey -> e.getKey().startsWith(springKey)) ? + e.getKey().substring(configServicePropertiesKey.toCharArray().length + 1) : + e.getKey(), + Map.Entry::getValue) + ); + + filterKeyMap.forEach((key, value) -> + logger.info("Adding property from config service in spring context: {} -> {}", key, value)); + + addJsonPropertySource(environment, new MapPropertySource(configServicePropertiesKey, filterKeyMap)); + } + + } + + @Override + public int getOrder() { + return DEFAULT_ORDER; + } + + + private void addJsonPropertySource(final ConfigurableEnvironment environment, final PropertySource<?> source) { + final MutablePropertySources sources = environment.getPropertySources(); + final String name = findPropertySource(sources); + if (sources.contains(name)) { + sources.addBefore(name, source); + } else { + sources.addFirst(source); + } + } + + private String findPropertySource(final MutablePropertySources sources) { + if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null) && + sources.contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) { + return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME; + + } + return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME; + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/spring/MongoAutoConfigurationPostProcessor.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/spring/MongoAutoConfigurationPostProcessor.java new file mode 100644 index 0000000..94f877e --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/spring/MongoAutoConfigurationPostProcessor.java @@ -0,0 +1,95 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.spring; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.onap.dcae.analytics.model.AnalyticsProfile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; + +/** + * Disables mongo auto configuration if {@link AnalyticsProfile#MONGO_PROFILE_NAME} is not present + * + * @author Rajiv Singla + */ +public class MongoAutoConfigurationPostProcessor implements EnvironmentPostProcessor, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(MongoAutoConfigurationPostProcessor.class); + + private static final String PROPERTY_SOURCE_NAME = "defaultProperties"; + private static final String SPRING_AUTO_CONFIG_EXCLUDE_PROPERTY_KEY = "spring.autoconfigure.exclude"; + private static final List<String> MONGO_AUTO_CONFIG_PROPERTIES = Arrays.asList( + "org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration", + "org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration", + "org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration"); + + @Override + public void postProcessEnvironment(final ConfigurableEnvironment environment, final SpringApplication application) { + + final boolean isMongoProfileActive = Arrays.stream(environment.getActiveProfiles()) + .anyMatch(profile -> profile.equalsIgnoreCase(AnalyticsProfile.MONGO_PROFILE_NAME)); + + // if mongo profile is not active disable mongo auto configuration + if (!isMongoProfileActive) { + logger.info("Mongo Profile is not active - disabling Mongo Auto Configuration"); + final Map<String, Object> mongoExcludePropsMap = new HashMap<>(); + mongoExcludePropsMap.put(SPRING_AUTO_CONFIG_EXCLUDE_PROPERTY_KEY, MONGO_AUTO_CONFIG_PROPERTIES); + addMongoPropertiesIfAbsent(environment.getPropertySources(), mongoExcludePropsMap); + } + } + + private void addMongoPropertiesIfAbsent(final MutablePropertySources propertySources, + final Map<String, Object> mongoPropertiesMap) { + MapPropertySource target = null; + if (propertySources.contains(PROPERTY_SOURCE_NAME)) { + PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME); + if (source instanceof MapPropertySource) { + target = (MapPropertySource) source; + for (final Map.Entry<String, Object> entry : mongoPropertiesMap.entrySet()) { + if (!target.containsProperty(entry.getKey())) { + target.getSource().putIfAbsent(entry.getKey(), entry.getValue()); + } + } + } + } + if (target == null) { + target = new MapPropertySource(PROPERTY_SOURCE_NAME, mongoPropertiesMap); + } + if (!propertySources.contains(PROPERTY_SOURCE_NAME)) { + propertySources.addLast(target); + } + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/AnalyticsHttpUtils.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/AnalyticsHttpUtils.java new file mode 100644 index 0000000..96e16f9 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/AnalyticsHttpUtils.java @@ -0,0 +1,124 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.util; + +/** + * @author Rajiv Singla + */ + +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Optional; + +import javax.annotation.Nullable; + +import org.onap.dcae.analytics.model.AnalyticsHttpConstants; +import org.onap.dcae.analytics.model.AnalyticsModelConstants; +import org.onap.dcae.analytics.model.configbindingservice.ConfigBindingServiceConstants; +import org.onap.dcae.analytics.model.util.supplier.CreationTimestampSupplier; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.messaging.MessageHeaders; + +/** + * Provides utility methods for Analytics HTTP Operations + * + * @author Rajiv Singla + */ +public abstract class AnalyticsHttpUtils { + + /** + * Creates default http headers for analytics http requests to other services like DMaaP, AAI etc with randomly + * generated request id header + * + * @return default analytics http headers + */ + public static HttpHeaders createDefaultHttpHeaders() { + return createDefaultHttpHeaders(null); + } + + /** + * Creates default http headers for analytics http requests to other services like DMaaP, AAI etc + * + * @param requestId request id + * + * @return default analytics http headers + */ + public static HttpHeaders createDefaultHttpHeaders(@Nullable final String requestId) { + + final HttpHeaders httpHeaders = new HttpHeaders(); + + // set analytics from app name header. + // Look up service name set by config service "SERVICE_NAME" environment variable or assign default value + httpHeaders.add( + AnalyticsHttpConstants.REQUEST_APP_NAME_HEADER_KEY, + ConfigBindingServiceConstants.SERVICE_NAME_ENV_VARIABLE_VALUE != null ? + ConfigBindingServiceConstants.SERVICE_NAME_ENV_VARIABLE_VALUE : + AnalyticsHttpConstants.REQUEST_APP_NAME_HEADER_DEFAULT_VALUE); + + // if request id is not present create random UUID + httpHeaders.add(AnalyticsHttpConstants.REQUEST_ID_HEADER_KEY, requestId != null ? + requestId : AnalyticsWebUtils.REQUEST_ID_SUPPLIER.get()); + + // sub transaction id is created randomly for each http request + httpHeaders.add(AnalyticsHttpConstants.REQUEST_TRANSACTION_ID_HEADER_KEY, + AnalyticsWebUtils.RANDOM_ID_SUPPLIER.get()); + + // by default analytics will accept only json + httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + httpHeaders.setAcceptCharset( + Collections.singletonList(Charset.forName(AnalyticsModelConstants.UTF8_CHARSET_NAME))); + + return httpHeaders; + } + + + public static String getRequestId(final MessageHeaders messageHeaders) { + return Optional.ofNullable(messageHeaders.get(AnalyticsHttpConstants.REQUEST_ID_HEADER_KEY)) + .map(requestId -> (String) requestId).orElse("UNKNOWN-REQUEST_ID"); + } + + public static String getTransactionId(final MessageHeaders messageHeaders) { + return Optional.ofNullable(messageHeaders.get(AnalyticsHttpConstants.REQUEST_TRANSACTION_ID_HEADER_KEY)) + .map(transactionId -> (String) transactionId).orElse("UNKNOWN-TRANSACTION_ID"); + } + + public static Date getTimestampFromHeaders(final Map<String, Object> headers, final String headerTsKey) { + return Optional.ofNullable(headers.get(headerTsKey)) + .map(ts -> CreationTimestampSupplier.getParsedDate((String) ts)).orElse(new Date()); + } + + public static String getRequestId(final HttpHeaders httpHeaders) { + return Optional.ofNullable( + httpHeaders.get(AnalyticsHttpConstants.REQUEST_ID_HEADER_KEY)).map(headerList -> + headerList.get(0)).orElse(AnalyticsWebUtils.REQUEST_ID_SUPPLIER.get()); + } + + public static String getTransactionId(final HttpHeaders httpHeaders) { + return Optional.ofNullable( + httpHeaders.get(AnalyticsHttpConstants.REQUEST_TRANSACTION_ID_HEADER_KEY)).map(headerList -> + headerList.get(0)).orElse(AnalyticsWebUtils.RANDOM_ID_SUPPLIER.get()); + } + + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/AnalyticsWebUtils.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/AnalyticsWebUtils.java new file mode 100644 index 0000000..a0bf558 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/AnalyticsWebUtils.java @@ -0,0 +1,42 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.util; + +import java.util.function.Supplier; + +import org.onap.dcae.analytics.model.AnalyticsModelConstants; +import org.onap.dcae.analytics.model.util.supplier.AnalyticsRequestIdSupplier; +import org.onap.dcae.analytics.model.util.supplier.CreationTimestampSupplier; +import org.onap.dcae.analytics.model.util.supplier.RandomIdSupplier; + +/** + * @author Rajiv Singla + */ +public abstract class AnalyticsWebUtils { + + public static final Supplier<String> REQUEST_ID_SUPPLIER = new AnalyticsRequestIdSupplier(); + public static final Supplier<String> RANDOM_ID_SUPPLIER = + new RandomIdSupplier(AnalyticsModelConstants.DEFAULT_RANDOM_ID_LENGTH); + public static final Supplier<String> CREATION_TIMESTAMP_SUPPLIER = new CreationTimestampSupplier(); + + private AnalyticsWebUtils() { + // private constructor + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/ValidationUtils.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/ValidationUtils.java new file mode 100644 index 0000000..5c734d8 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/ValidationUtils.java @@ -0,0 +1,108 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.util; + + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.onap.dcae.analytics.web.exception.AnalyticsValidationException; +import org.onap.dcae.analytics.web.validation.AnalyticsValidator; +import org.onap.dcae.analytics.web.validation.ValidationResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +/** + * Validation Utilities + * + * @author Rajiv Singla + */ +public abstract class ValidationUtils { + + private static final Logger log = LoggerFactory.getLogger(ValidationUtils.class); + + private ValidationUtils() { + + } + + /** + * Checks if String is empty. For null string true is returned + * + * @param stringValue string value + * + * @return returns true is string is empty or null + */ + public static boolean isEmpty(@Nullable final String stringValue) { + return stringValue == null || stringValue.isEmpty() || stringValue.trim().isEmpty(); + } + + + /** + * Checks if String value is present. A null, empty, or blank values of string + * are considered not present. + * + * @param stringValue string value to check if it is present or not + * + * @return true if string value is not null, empty or blank + */ + public static boolean isPresent(@Nullable final String stringValue) { + return !isEmpty(stringValue); + } + + + /** + * Provides common functionality to validate analytics objects. + * Throws {@link AnalyticsValidationException} exception if validation fails + * + * @param targetObject target object that needs to be validated + * @param validator validator that will be used to validate the target object + * @param <T> target object type that needs to be validated + * @param <R> Validation Response type + * @param <V> Validator Type + * + * @return target object if validation is successful + */ + public static <T, R extends ValidationResponse, V extends AnalyticsValidator<T, R>> T validate( + @Nonnull final T targetObject, + @Nonnull final V validator) { + + Assert.notNull(targetObject, "target object that needs to validated must not be null"); + Assert.notNull(validator, "validator must not be null"); + + final String targetObjectClass = targetObject.getClass().getSimpleName(); + final String validatorClass = validator.getClass().getSimpleName(); + + log.debug("Validating target object of type: {} with validator type: {} ", targetObjectClass, validatorClass); + + final R validationResponse = validator.apply(targetObject); + + // If setting validation fails throw an exception + if (validationResponse.hasErrors()) { + throw new AnalyticsValidationException(validationResponse.getAllErrorMessage(), + new IllegalArgumentException(validationResponse.getAllErrorMessage())); + } + + log.info("Validation Successful for target object type: {} with validator type: {}", targetObjectClass, + validatorClass); + + return targetObject; + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/function/MrSubscriberURLFunction.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/function/MrSubscriberURLFunction.java new file mode 100644 index 0000000..2f32d6d --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/util/function/MrSubscriberURLFunction.java @@ -0,0 +1,106 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.util.function; + + +import static org.onap.dcae.analytics.web.util.AnalyticsWebUtils.RANDOM_ID_SUPPLIER; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.onap.dcae.analytics.model.DmaapMrConstants; +import org.onap.dcae.analytics.web.dmaap.MrSubscriberPreferences; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Creates DMaaP MR Subscriber URLs from {@link MrSubscriberPreferences} + * + * @author Rajiv Singla + */ +public class MrSubscriberURLFunction implements Function<MrSubscriberPreferences, List<URL>> { + + private static final Logger logger = LoggerFactory.getLogger(MrSubscriberURLFunction.class); + private static final String URL_PATH_SEPARATOR = "/"; + + @Override + public List<URL> apply(final MrSubscriberPreferences subscriberConfig) { + + final List<URL> subscriberURLs = new LinkedList<>(); + + // if consumer ids is not present generate single random consumer id + final List<String> consumerIds = subscriberConfig.getConsumerIds() != null ? + subscriberConfig.getConsumerIds() : Stream.of(RANDOM_ID_SUPPLIER.get()).collect(Collectors.toList()); + + for (final String consumerId : consumerIds) { + + // request url must be present + final String requestURL = subscriberConfig.getRequestURL(); + + // generate random consumer group if not present + final String consumerGroup = subscriberConfig.getConsumerGroup() != null ? + subscriberConfig.getConsumerGroup() : + DmaapMrConstants.SUBSCRIBER_RANDOM_CONSUMER_GROUP_PREFIX + RANDOM_ID_SUPPLIER.get(); + + // set default message limit if not present + final Integer messageLimit = subscriberConfig.getMessageLimit() != null ? + subscriberConfig.getMessageLimit() : DmaapMrConstants.SUBSCRIBER_DEFAULT_MESSAGE_LIMIT; + + // set default timeout if not present + final Integer timeout = subscriberConfig.getTimeout() != null ? + subscriberConfig.getTimeout() : DmaapMrConstants.SUBSCRIBER_DEFAULT_TIMEOUT; + + final UriComponentsBuilder componentsBuilder = UriComponentsBuilder + .fromHttpUrl(requestURL) + .path(URL_PATH_SEPARATOR + consumerGroup + URL_PATH_SEPARATOR + consumerId); + + if (messageLimit != null && messageLimit >= 1) { + componentsBuilder + .queryParam(DmaapMrConstants.SUBSCRIBER_MSG_LIMIT_QUERY_PARAM_NAME, messageLimit); + } + + if (timeout != null && timeout >= 1) { + componentsBuilder + .queryParam(DmaapMrConstants.SUBSCRIBER_TIMEOUT_QUERY_PARAM_NAME, timeout); + } + + subscriberURLs.add(createURL(componentsBuilder)); + + } + + return subscriberURLs; + } + + private URL createURL(final UriComponentsBuilder uriComponentsBuilder) { + try { + final URL subscriberURL = uriComponentsBuilder.build().toUri().toURL(); + logger.info("Created DMaaP MR Subscriber URL: {}", subscriberURL); + return subscriberURL; + } catch (MalformedURLException e) { + throw new IllegalStateException("Unable to build DMaaP MR Subscriber URL", e); + } + } +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/AnalyticsValidator.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/AnalyticsValidator.java new file mode 100644 index 0000000..0eaa800 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/AnalyticsValidator.java @@ -0,0 +1,39 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.validation; + +import java.io.Serializable; +import java.util.function.Function; + +import org.springframework.validation.Validator; + +/** + * Analytics Validator can be used to validate Analytics components + * + * @param <T> object class that needs too be validated + * @param <R> validation Response + * + * @author Rajiv Singla + */ +public interface AnalyticsValidator<T, R extends ValidationResponse> extends Function<T, R>, Validator, Serializable { + +} + + diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/GenericValidationResponse.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/GenericValidationResponse.java new file mode 100644 index 0000000..d5da8f2 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/GenericValidationResponse.java @@ -0,0 +1,80 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.validation; + +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A generic implementation of Validation Response + * + * @author Rajiv Singla + */ +@ToString +@EqualsAndHashCode +public class GenericValidationResponse implements ValidationResponse { + + private static final String DEFAULT_DELIMITER = ","; + + private LinkedHashMap<String, String> errorMessageMap = new LinkedHashMap<>(); + + @Override + public boolean hasErrors() { + return !errorMessageMap.isEmpty(); + } + + @Override + public Set<String> getFieldNamesWithError() { + return errorMessageMap.keySet(); + } + + @Override + public Collection<String> getErrorMessages() { + return errorMessageMap.values(); + } + + @Override + public Map<String, String> getValidationResultsAsMap() { + return errorMessageMap; + } + + @Override + public String getAllErrorMessage() { + return getAllErrorMessage(DEFAULT_DELIMITER); + } + + @Override + public String getAllErrorMessage(String delimiter) { + return errorMessageMap.values() + .stream().collect(Collectors.joining(delimiter)); + } + + @Override + public void addErrorMessage(String fieldName, String filedErrorMessage) { + errorMessageMap.put(fieldName, filedErrorMessage); + } + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/ValidationResponse.java b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/ValidationResponse.java new file mode 100644 index 0000000..bf06263 --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/java/org/onap/dcae/analytics/web/validation/ValidationResponse.java @@ -0,0 +1,88 @@ +/* + * ================================================================================ + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + */ + +package org.onap.dcae.analytics.web.validation; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Validation Response + * + * @author Rajiv Singla + */ +public interface ValidationResponse { + + /** + * Returns true if validation resulted in one or more errors + * + * @return true if validation has errors + */ + boolean hasErrors(); + + /** + * Returns all field names which have error + * + * @return names of fields which have error + */ + Set<String> getFieldNamesWithError(); + + /** + * Returns list of all error messages + * + * @return list of error messages + */ + Collection<String> getErrorMessages(); + + + /** + * Returns all error messages as string delimited by comma + * + * @return all error messages delimited by given delimiter + */ + String getAllErrorMessage(); + + /** + * Returns all error messages as string delimited by given delimited + * + * @param delimiter delimited to be used for error message + * + * @return all error messages delimited by given delimiter + */ + String getAllErrorMessage(String delimiter); + + /** + * Adds field name and error message to the validation response + * + * @param fieldName field name which has validation error + * @param filedErrorMessage validation error message + */ + void addErrorMessage(String fieldName, String filedErrorMessage); + + + /** + * Returns validation results as map containing values as keys and values + * as error Message + * + * @return Map containing field names and error message associated with those fields + */ + Map<String, String> getValidationResultsAsMap(); + +} diff --git a/dcae-analytics/dcae-analytics-web/src/main/resources/META-INF/spring.factories b/dcae-analytics/dcae-analytics-web/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..23fe87e --- /dev/null +++ b/dcae-analytics/dcae-analytics-web/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +# Config Binding Service Environment Post processor +org.springframework.boot.env.EnvironmentPostProcessor=\ + org.onap.dcae.analytics.web.spring.ConfigBindingServiceEnvironmentPostProcessor,\ + org.onap.dcae.analytics.web.spring.MongoAutoConfigurationPostProcessor |