From e6e84ec1352fa397989b70cb08975d738c4076e3 Mon Sep 17 00:00:00 2001 From: Oleg Smelov <45400511+lumber1000@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:01:10 +0100 Subject: [PATCH] NPE fix (#254) --- README.md | 8 ++- build.gradle | 4 +- cradle-cassandra/build.gradle | 24 ++++----- .../AbstractMessageIteratorProvider.java | 42 ++++++++-------- .../CassandraStoredMessageFilter.java | 23 +++++---- .../MessageBatchesIteratorProvider.java | 9 ++-- .../messages/MessagesIteratorProvider.java | 12 ++--- .../MessagesIteratorProviderTest.java | 49 +++++++++++++++++++ cradle-core/build.gradle | 6 +-- .../testevents/StoredTestEventBatch.java | 11 +++-- gradle.properties | 2 +- 11 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProviderTest.java diff --git a/README.md b/README.md index 5350c4e11..267e440d9 100644 --- a/README.md +++ b/README.md @@ -191,4 +191,10 @@ A test event can have a reference to its parent, thus forming a hierarchical str Events in a batch can have a reference only to the parent of the batch or other test events from the same batch. Events outside the batch should not reference events within the batch. -Test events have mandatory parameters that are verified when storing an event. These are: id, name (for non-batch events), start timestamp. \ No newline at end of file +Test events have mandatory parameters that are verified when storing an event. These are: id, name (for non-batch events), start timestamp. + +## Changes + +### 5.1.5 + +* fixed: NullPointerException on AbstractMessageIteratorProvider creation for book with no pages in it. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3e6072d0c..0e7371513 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { - id "io.github.gradle-nexus.publish-plugin" version "1.0.0" - id "org.owasp.dependencycheck" version "8.2.1" + id "io.github.gradle-nexus.publish-plugin" version "1.3.0" + id "org.owasp.dependencycheck" version "9.0.9" id 'signing' } diff --git a/cradle-cassandra/build.gradle b/cradle-cassandra/build.gradle index e9bbfc0e5..f7deaaafd 100644 --- a/cradle-cassandra/build.gradle +++ b/cradle-cassandra/build.gradle @@ -1,7 +1,3 @@ -ext { - driver_version = '4.15.0' -} - configurations.configureEach { exclude group: 'com.esri.geometry' exclude group: 'org.apache.tinkerpop' @@ -13,24 +9,24 @@ dependencies { implementation "org.apache.commons:commons-lang3" implementation 'com.exactpro.th2:task-utils:0.1.2' - implementation "com.datastax.oss:java-driver-core:${driver_version}" - implementation "com.datastax.oss:java-driver-query-builder:${driver_version}" - implementation "com.datastax.oss:java-driver-mapper-processor:${driver_version}" - implementation "com.datastax.oss:java-driver-mapper-runtime:${driver_version}" + implementation "com.datastax.oss:java-driver-core" + implementation "com.datastax.oss:java-driver-query-builder" + implementation "com.datastax.oss:java-driver-mapper-processor" + implementation "com.datastax.oss:java-driver-mapper-runtime" implementation 'io.prometheus:simpleclient_dropwizard:0.16.0' implementation 'com.google.guava:guava' // this section is required to bypass failing vulnerability check caused by cassandra driver's transitive dependencies - annotationProcessor platform('com.exactpro.th2:bom:4.4.0') - annotationProcessor "com.datastax.oss:java-driver-mapper-processor:${driver_version}" + annotationProcessor platform('com.exactpro.th2:bom:4.5.0') + annotationProcessor "com.datastax.oss:java-driver-mapper-processor" // --- - testImplementation 'org.testng:testng:7.7.0' - testImplementation 'org.assertj:assertj-core:3.12.2' + testImplementation 'org.testng:testng:7.9.0' + testImplementation 'org.assertj:assertj-core:3.25.3' testImplementation "org.apache.logging.log4j:log4j-slf4j2-impl" testImplementation 'org.apache.logging.log4j:log4j-core' - testImplementation 'org.mockito:mockito-core:5.2.0' - testImplementation 'org.testcontainers:cassandra:1.17.5' + testImplementation 'org.mockito:mockito-core:5.10.0' + testImplementation 'org.testcontainers:cassandra:1.19.6' } def gen_dir = 'build/generated/sources/annotationProcessor/main' diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/AbstractMessageIteratorProvider.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/AbstractMessageIteratorProvider.java index aac8d6552..7f9ff2eab 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/AbstractMessageIteratorProvider.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/AbstractMessageIteratorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,11 +117,14 @@ public AbstractMessageIteratorProvider(String requestInfo, MessageFilter filter, } //TODO refactor this method to assigns firstPage outside the method - protected FilterForGreater createLeftBoundFilter(MessageFilter filter) throws CradleStorageException - { + protected FilterForGreater createLeftBoundFilter(MessageFilter filter) throws CradleStorageException { FilterForGreater result = filter.getTimestampFrom(); firstPage = FilterUtils.findFirstPage(filter.getPageId(), result, book); - Instant leftBoundFromPage = firstPage.getStarted(); + + if (result == null && firstPage == null) + return null; + + Instant leftBoundFromPage = firstPage == null ? Instant.MIN : firstPage.getStarted(); if (result == null || (filter.getPageId() != null && leftBoundFromPage.isAfter(result.getValue()))) return FilterForGreater.forGreaterOrEquals(leftBoundFromPage); @@ -132,8 +135,7 @@ protected FilterForGreater createLeftBoundFilter(MessageFilter filter) filter.getDirection().getLabel(), leftBoundLocalDate.toLocalDate(), leftBoundLocalDate.toLocalTime()); - if (nearestBatchTime != null) - { + if (nearestBatchTime != null) { Instant nearestBatchInstant = TimeUtils.toInstant(leftBoundLocalDate.toLocalDate(), nearestBatchTime); if (nearestBatchInstant.isBefore(result.getValue())) result = FilterForGreater.forGreaterOrEquals(nearestBatchInstant); @@ -175,21 +177,19 @@ private LocalTime getNearestBatchTime(PageInfo page, String sessionAlias, String } //TODO refactor this method to assign last page outside of this method. - protected FilterForLess createRightBoundFilter(MessageFilter filter) - { + protected FilterForLess createRightBoundFilter(MessageFilter filter) { FilterForLess result = filter.getTimestampTo(); lastPage = FilterUtils.findLastPage(filter.getPageId(), result, book); - Instant endOfPage = lastPage.getEnded() == null ? Instant.now() : lastPage.getEnded(); + Instant endOfPage = lastPage == null || lastPage.getEnded() == null ? Instant.now() : lastPage.getEnded(); return FilterForLess.forLessOrEquals(result == null || endOfPage.isBefore(result.getValue()) ? endOfPage : result.getValue()); } - protected CassandraStoredMessageFilter createInitialFilter(MessageFilter filter) - { + protected CassandraStoredMessageFilter createInitialFilter(MessageFilter filter) { if (filter.getOrder() == Order.DIRECT) { return new CassandraStoredMessageFilter( - firstPage.getId().getBookId().getName(), - firstPage.getId().getName(), + book.getId().getName(), + firstPage != null ? firstPage.getId().getName() : null, filter.getSessionAlias(), filter.getDirection().getLabel(), leftBoundFilter, @@ -198,8 +198,8 @@ protected CassandraStoredMessageFilter createInitialFilter(MessageFilter filter) filter.getOrder()); } else { return new CassandraStoredMessageFilter( - lastPage.getId().getBookId().getName(), - lastPage.getId().getName(), + book.getId().getName(), + lastPage != null ? lastPage.getId().getName() : null, filter.getSessionAlias(), filter.getDirection().getLabel(), leftBoundFilter, @@ -207,8 +207,6 @@ protected CassandraStoredMessageFilter createInitialFilter(MessageFilter filter) filter.getLimit(), filter.getOrder()); } - - } protected CassandraStoredMessageFilter createNextFilter(CassandraStoredMessageFilter prevFilter, int updatedLimit) @@ -240,22 +238,22 @@ protected CassandraStoredMessageFilter createNextFilter(CassandraStoredMessageFi filter.getOrder()); } - protected boolean performNextIteratorChecks () { + protected boolean interruptIteratorChecks () { if (cassandraFilter == null) { - return false; + return true; } if (takeWhileIterator != null && takeWhileIterator.isHalted()) { logger.debug("Iterator was interrupted because iterator condition was not met"); - return false; + return true; } if (limit > 0 && returned.get() >= limit) { logger.debug("Filtering interrupted because limit for records to return ({}) is reached ({})", limit, returned); - return false; + return true; } - return true; + return false; } protected Iterator getBatchedIterator (MappedAsyncPagingIterable resultSet) { diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/CassandraStoredMessageFilter.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/CassandraStoredMessageFilter.java index e1d398c39..4ac1e81f6 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/CassandraStoredMessageFilter.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/CassandraStoredMessageFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import com.datastax.oss.driver.api.querybuilder.relation.MultiColumnRelationBuilder; import com.datastax.oss.driver.api.querybuilder.select.Select; -import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.exactpro.cradle.Order; import com.exactpro.cradle.cassandra.dao.CassandraFilter; import com.exactpro.cradle.cassandra.utils.FilterUtils; @@ -84,14 +83,16 @@ public CassandraStoredMessageFilter(String book, String page, String sessionAlia } @Override - public Select addConditions(Select select) - { + public Select addConditions(Select select) { select = select .whereColumn(FIELD_BOOK).isEqualTo(bindMarker()) - .whereColumn(FIELD_PAGE).isEqualTo(bindMarker()) .whereColumn(FIELD_SESSION_ALIAS).isEqualTo(bindMarker()) .whereColumn(FIELD_DIRECTION).isEqualTo(bindMarker()); - + + if (page != null) { + select = select.whereColumn(FIELD_PAGE).isEqualTo(bindMarker()); + } + if (sequence != null) select = addMessageIdConditions(select); else @@ -115,14 +116,16 @@ public Select addConditions(Select select) } @Override - public BoundStatementBuilder bindParameters(BoundStatementBuilder builder) - { + public BoundStatementBuilder bindParameters(BoundStatementBuilder builder) { builder = builder .setString(FIELD_BOOK, book) - .setString(FIELD_PAGE, page) .setString(FIELD_SESSION_ALIAS, sessionAlias) .setString(FIELD_DIRECTION, direction); - + + if (page != null) { + builder = builder.setString(FIELD_PAGE, page); + } + if (sequence != null) builder = bindMessageIdParameters(builder); else diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessageBatchesIteratorProvider.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessageBatchesIteratorProvider.java index 2ce9d4610..4697df9cc 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessageBatchesIteratorProvider.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessageBatchesIteratorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,15 +38,14 @@ public class MessageBatchesIteratorProvider extends AbstractMessageIteratorProvi public MessageBatchesIteratorProvider(String requestInfo, MessageFilter filter, CassandraOperators operators, BookInfo book, ExecutorService composingService, SelectQueryExecutor selectQueryExecutor, - Function readAttrs) throws CradleStorageException - { + Function readAttrs) throws CradleStorageException { super(requestInfo, filter, operators, book, composingService, selectQueryExecutor, readAttrs); } @Override public CompletableFuture> nextIterator() { - if (!performNextIteratorChecks()) { + if (interruptIteratorChecks()) { return CompletableFuture.completedFuture(null); } @@ -55,4 +54,4 @@ public CompletableFuture> nextIterator() { .thenApplyAsync(this::getBatchedIterator, composingService) .thenApply(it -> limit > 0 ? new LimitedIterator<>(it, limit) : it); } -} +} \ No newline at end of file diff --git a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProvider.java b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProvider.java index c9370a980..3e378943f 100644 --- a/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProvider.java +++ b/cradle-cassandra/src/main/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2023 Exactpro (Exactpro Systems Limited) + * Copyright 2021-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,15 +36,13 @@ public class MessagesIteratorProvider extends AbstractMessageIteratorProvider readAttrs) throws CradleStorageException - { + Function readAttrs) throws CradleStorageException { super(requestInfo, filter, operators, book, composingService, selectQueryExecutor, readAttrs); } @Override - public CompletableFuture> nextIterator() - { - if (!performNextIteratorChecks()) { + public CompletableFuture> nextIterator() { + if (interruptIteratorChecks()) { return CompletableFuture.completedFuture(null); } @@ -53,4 +51,4 @@ public CompletableFuture> nextIterator() .thenApplyAsync(this::getBatchedIterator, composingService) .thenApplyAsync(it -> new FilteredMessageIterator(it, filter, limit, returned), composingService); } -} +} \ No newline at end of file diff --git a/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProviderTest.java b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProviderTest.java new file mode 100644 index 000000000..78e1baec6 --- /dev/null +++ b/cradle-cassandra/src/test/java/com/exactpro/cradle/cassandra/dao/messages/MessagesIteratorProviderTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.cradle.cassandra.dao.messages; + +import com.exactpro.cradle.BookId; +import com.exactpro.cradle.BookInfo; +import com.exactpro.cradle.Direction; +import com.exactpro.cradle.cassandra.dao.CassandraOperators; +import com.exactpro.cradle.messages.MessageFilter; +import com.exactpro.cradle.utils.CradleStorageException; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import java.time.Instant; +import java.util.Collections; + +public class MessagesIteratorProviderTest { + protected static final BookId DEFAULT_BOOK_ID = new BookId("test_book"); + private static final String FIRST_SESSION_ALIAS = "test_session_alias"; + + @Test(description = "Create iterator provider with empty book") + public void createIteratorProviderWithEmptyBook() throws CradleStorageException { + CassandraOperators operators = Mockito.mock(CassandraOperators.class); + MessageFilter messageFilter = new MessageFilter(DEFAULT_BOOK_ID, FIRST_SESSION_ALIAS, Direction.FIRST, null); + new MessagesIteratorProvider( + "", + messageFilter, + operators, + new BookInfo(DEFAULT_BOOK_ID, "book_name", "", Instant.now(), Collections.emptyList()), + null, + null, + null + ); + } +} \ No newline at end of file diff --git a/cradle-core/build.gradle b/cradle-core/build.gradle index 190437e62..e84ff9f96 100644 --- a/cradle-core/build.gradle +++ b/cradle-core/build.gradle @@ -1,5 +1,5 @@ dependencies { - api platform('com.exactpro.th2:bom:4.4.0') + api platform('com.exactpro.th2:bom:4.5.0') implementation "com.fasterxml.jackson.core:jackson-databind" implementation "org.apache.commons:commons-lang3" @@ -10,8 +10,8 @@ dependencies { testImplementation 'org.apache.logging.log4j:log4j-slf4j2-impl' testImplementation 'org.apache.logging.log4j:log4j-core' - testImplementation 'org.testng:testng:7.7.0' - testImplementation 'org.assertj:assertj-core:3.12.2' + testImplementation 'org.testng:testng:7.9.0' + testImplementation 'org.assertj:assertj-core:3.25.3' } test { diff --git a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java index 8e51d073d..886c54667 100644 --- a/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java +++ b/cradle-core/src/main/java/com/exactpro/cradle/testevents/StoredTestEventBatch.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Exactpro (Exactpro Systems Limited) + * Copyright 2020-2024 Exactpro (Exactpro Systems Limited) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.exactpro.cradle.testevents; import java.time.Instant; +import java.util.List; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -54,7 +55,7 @@ public StoredTestEventBatch(StoredTestEventId id, String name, String type, Stor super(id, name, type, parentId, pageId, error, recDate); Map allEvents = new LinkedHashMap<>(); - Collection roots = new ArrayList<>(); + List roots = new ArrayList<>(); Map> childrenPerEvent = new LinkedHashMap<>(); Map> batchMessages = new HashMap<>(); Instant end = null; @@ -78,7 +79,7 @@ public StoredTestEventBatch(StoredTestEventId id, String name, String type, Stor Set eventMessages = messages != null ? messages.get(child.getId()) : null; if (eventMessages != null) - batchMessages.put(child.getId(), Collections.unmodifiableSet(new HashSet<>(eventMessages))); + batchMessages.put(child.getId(), Set.copyOf(eventMessages)); Instant eventEnd = child.getEndTimestamp(); if (eventEnd != null) @@ -93,7 +94,7 @@ public StoredTestEventBatch(StoredTestEventId id, String name, String type, Stor } this.events = Collections.unmodifiableMap(allEvents); - this.rootEvents = Collections.unmodifiableCollection(roots); + this.rootEvents = Collections.unmodifiableList(roots); this.children = Collections.unmodifiableMap(childrenPerEvent); this.messages = Collections.unmodifiableMap(batchMessages); this.endTimestamp = end; @@ -123,7 +124,7 @@ public boolean isSuccess() public Set getMessages() { Set result = new HashSet<>(); - messages.values().forEach(c -> result.addAll(c)); + messages.values().forEach(result::addAll); return result; } diff --git a/gradle.properties b/gradle.properties index 60a79290a..2d0fef2e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -release_version=5.1.4 +release_version=5.1.5 description='Cradle API' vcs_url=https://github.com/th2-net/cradleapi