>, T> {
}
@@ -221,11 +172,14 @@ private interface NotionCompoundFilterSetter
*
* Returns entries that match all of the provided filters.
*/
- public static final class AndFilter extends CompoundFilter {
+ public static final class AndFilter extends Filter {
+
+ @JsonProperty
+ @JsonTypeInfo(use = Id.DEDUCTION)
+ private final List and = new ArrayList<>();
private AndFilter(Filter first, Filter second) {
- super(notion.api.v1.model.databases.query.filter.CompoundFilter::setAnd);
- filters.addAll(List.of(first, second));
+ and.addAll(List.of(first, second));
}
/**
@@ -234,8 +188,8 @@ private AndFilter(Filter first, Filter second) {
* @return a {@link FilterConditionBuilder} instance for an {@link AndFilter}
*/
public FilterConditionBuilder and() {
- return new FilterConditionBuilder<>((property, customizer) -> {
- filters.add(new PropertyFilter(property, customizer));
+ return new FilterConditionBuilder<>((property, condition) -> {
+ and.add(new PropertyFilter(property, condition));
return this;
});
}
@@ -246,7 +200,7 @@ public FilterConditionBuilder and() {
* @return a new {@link AndFilter} instance
*/
public AndFilter and(Filter filter) {
- filters.add(Objects.requireNonNull(filter));
+ and.add(Objects.requireNonNull(filter));
return this;
}
@@ -258,11 +212,14 @@ public AndFilter and(Filter filter) {
*
* Returns entries that match any of the provided filters.
*/
- public static final class OrFilter extends CompoundFilter {
+ public static final class OrFilter extends Filter {
+
+ @JsonProperty
+ @JsonTypeInfo(use = Id.DEDUCTION)
+ private final List or = new ArrayList<>();
private OrFilter(Filter first, Filter second) {
- super(notion.api.v1.model.databases.query.filter.CompoundFilter::setOr);
- filters.addAll(List.of(first, second));
+ or.addAll(List.of(first, second));
}
/**
@@ -271,8 +228,8 @@ private OrFilter(Filter first, Filter second) {
* @return a {@link FilterConditionBuilder} instance for an {@link OrFilter}
*/
public FilterConditionBuilder or() {
- return new FilterConditionBuilder<>((property, customizer) -> {
- filters.add(new PropertyFilter(property, customizer));
+ return new FilterConditionBuilder<>((property, condition) -> {
+ or.add(new PropertyFilter(property, condition));
return this;
});
}
@@ -283,7 +240,7 @@ public FilterConditionBuilder or() {
* @return a new {@link OrFilter} instance
*/
public OrFilter or(Filter filter) {
- filters.add(Objects.requireNonNull(filter));
+ or.add(Objects.requireNonNull(filter));
return this;
}
@@ -296,9 +253,9 @@ public OrFilter or(Filter filter) {
*/
public static final class FilterConditionBuilder {
- private final NotionPropertyFilterFactory factory;
+ private final PropertyFilterFactory factory;
- private FilterConditionBuilder(NotionPropertyFilterFactory factory) {
+ private FilterConditionBuilder(PropertyFilterFactory factory) {
this.factory = factory;
}
@@ -316,7 +273,7 @@ public CheckboxCondition checkbox(String property) {
* Start the definition of the filter condition for a {@code files} property.
* @param property The name of the property as it appears in the database, or the
* property ID
- * @return a new {@link CheckboxCondition} instance
+ * @return a new {@link FilesCondition} instance
*/
public FilesCondition files(String property) {
return new FilesCondition<>(property, factory);
@@ -363,19 +320,24 @@ public StatusCondition status(String property) {
return new StatusCondition<>(property, factory);
}
+ @JsonNaming(SnakeCaseStrategy.class)
+ @JsonInclude(Include.NON_EMPTY)
static abstract sealed class Condition {
+ private final String name;
+
private final String property;
- private final NotionPropertyFilterFactory factory;
+ private final PropertyFilterFactory factory;
- private Condition(String property, NotionPropertyFilterFactory factory) {
+ private Condition(String name, String property, PropertyFilterFactory factory) {
+ this.name = name;
this.property = property;
this.factory = factory;
}
- T toFilter(NotionPropertyFilterCustomizer customizer) {
- return factory.apply(property, customizer);
+ T toFilter() {
+ return factory.apply(property, new SimpleEntry<>(name, this));
}
}
@@ -387,8 +349,16 @@ T toFilter(NotionPropertyFilterCustomizer customizer) {
*/
public static final class CheckboxCondition extends Condition {
- private CheckboxCondition(String property, NotionPropertyFilterFactory factory) {
- super(property, factory);
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean equals;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean doesNotEqual;
+
+ private CheckboxCondition(String property, PropertyFilterFactory factory) {
+ super("checkbox", property, factory);
}
/**
@@ -397,9 +367,8 @@ private CheckboxCondition(String property, NotionPropertyFilterFactory factor
* @return a filter with the newly defined condition
*/
public T isEqualTo(boolean value) {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setCheckbox(checkboxFilter));
+ this.equals = value;
+ return toFilter();
}
/**
@@ -408,9 +377,8 @@ public T isEqualTo(boolean value) {
* @return a filter with the newly defined condition
*/
public T isNotEqualTo(boolean value) {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setDoesNotEqual(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setCheckbox(checkboxFilter));
+ this.doesNotEqual = value;
+ return toFilter();
}
}
@@ -422,8 +390,16 @@ public T isNotEqualTo(boolean value) {
*/
public static final class FilesCondition extends Condition {
- private FilesCondition(String property, NotionPropertyFilterFactory factory) {
- super(property, factory);
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isEmpty;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isNotEmpty;
+
+ private FilesCondition(String property, PropertyFilterFactory factory) {
+ super("files", property, factory);
}
/**
@@ -431,9 +407,8 @@ private FilesCondition(String property, NotionPropertyFilterFactory factory)
* @return a filter with the newly defined condition
*/
public T isEmpty() {
- FilesFilter filesFilter = new FilesFilter();
- filesFilter.setEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setFile(filesFilter));
+ this.isEmpty = true;
+ return toFilter();
}
/**
@@ -441,9 +416,8 @@ public T isEmpty() {
* @return a filter with the newly defined condition
*/
public T isNotEmpty() {
- FilesFilter filesFilter = new FilesFilter();
- filesFilter.setNotEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setFile(filesFilter));
+ this.isNotEmpty = true;
+ return toFilter();
}
}
@@ -455,8 +429,24 @@ public T isNotEmpty() {
*/
public static final class MultiSelectCondition extends Condition {
- private MultiSelectCondition(String property, NotionPropertyFilterFactory factory) {
- super(property, factory);
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable String contains;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable String doesNotContain;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isEmpty;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isNotEmpty;
+
+ private MultiSelectCondition(String property, PropertyFilterFactory factory) {
+ super("multi_select", property, factory);
}
/**
@@ -466,9 +456,8 @@ private MultiSelectCondition(String property, NotionPropertyFilterFactory fac
* @return a filter with the newly defined condition
*/
public T contains(String value) {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setContains(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setMultiSelect(multiSelectFilter));
+ this.contains = value;
+ return toFilter();
}
/**
@@ -478,9 +467,8 @@ public T contains(String value) {
* @return a filter with the newly defined condition
*/
public T doesNotContain(String value) {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setDoesNotContain(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setMultiSelect(multiSelectFilter));
+ this.doesNotContain = value;
+ return toFilter();
}
/**
@@ -488,9 +476,8 @@ public T doesNotContain(String value) {
* @return a filter with the newly defined condition
*/
public T isEmpty() {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setMultiSelect(multiSelectFilter));
+ this.isEmpty = true;
+ return toFilter();
}
/**
@@ -498,9 +485,8 @@ public T isEmpty() {
* @return a filter with the newly defined condition
*/
public T isNotEmpty() {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setNotEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setMultiSelect(multiSelectFilter));
+ this.isNotEmpty = true;
+ return toFilter();
}
}
@@ -512,8 +498,40 @@ public T isNotEmpty() {
*/
public static final class NumberCondition extends Condition {
- private NumberCondition(String property, NotionPropertyFilterFactory factory) {
- super(property, factory);
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Integer equals;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Integer doesNotEqual;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Integer greaterThan;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Integer greaterThanOrEqualTo;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Integer lessThan;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Integer lessThanOrEqualTo;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isEmpty;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isNotEmpty;
+
+ private NumberCondition(String property, PropertyFilterFactory factory) {
+ super("number", property, factory);
}
/**
@@ -523,9 +541,8 @@ private NumberCondition(String property, NotionPropertyFilterFactory factory)
* @return a filter with the newly defined condition
*/
public T isEqualTo(int value) {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setEquals(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.equals = value;
+ return toFilter();
}
/**
@@ -535,9 +552,8 @@ public T isEqualTo(int value) {
* @return a filter with the newly defined condition
*/
public T isNotEqualTo(int value) {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setDoesNotEqual(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.doesNotEqual = value;
+ return toFilter();
}
/**
@@ -546,9 +562,8 @@ public T isNotEqualTo(int value) {
* @return a filter with the newly defined condition
*/
public T isGreaterThan(int value) {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setGreaterThan(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.greaterThan = value;
+ return toFilter();
}
/**
@@ -558,9 +573,8 @@ public T isGreaterThan(int value) {
* @return a filter with the newly defined condition
*/
public T isGreaterThanOrEqualTo(int value) {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setGreaterThanOrEqualTo(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.greaterThanOrEqualTo = value;
+ return toFilter();
}
/**
@@ -570,9 +584,8 @@ public T isGreaterThanOrEqualTo(int value) {
* @return a filter with the newly defined condition
*/
public T isLessThan(int value) {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setLessThan(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.lessThan = value;
+ return toFilter();
}
/**
@@ -582,9 +595,8 @@ public T isLessThan(int value) {
* @return a filter with the newly defined condition
*/
public T isLessThanOrEqualTo(int value) {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setLessThanOrEqualTo(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.lessThanOrEqualTo = value;
+ return toFilter();
}
/**
@@ -592,9 +604,8 @@ public T isLessThanOrEqualTo(int value) {
* @return a filter with the newly defined condition
*/
public T isEmpty() {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.isEmpty = true;
+ return toFilter();
}
/**
@@ -602,9 +613,8 @@ public T isEmpty() {
* @return a filter with the newly defined condition
*/
public T isNotEmpty() {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setNotEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setNumber(numberFilter));
+ this.isNotEmpty = true;
+ return toFilter();
}
}
@@ -616,8 +626,24 @@ public T isNotEmpty() {
*/
public static final class SelectCondition extends Condition {
- private SelectCondition(String property, NotionPropertyFilterFactory factory) {
- super(property, factory);
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable String equals;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable String doesNotEqual;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isEmpty;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isNotEmpty;
+
+ private SelectCondition(String property, PropertyFilterFactory factory) {
+ super("select", property, factory);
}
/**
@@ -626,9 +652,8 @@ private SelectCondition(String property, NotionPropertyFilterFactory factory)
* @return a filter with the newly defined condition
*/
public T isEqualTo(String value) {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEquals(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setSelect(selectFilter));
+ this.equals = value;
+ return toFilter();
}
/**
@@ -638,9 +663,8 @@ public T isEqualTo(String value) {
* @return a filter with the newly defined condition
*/
public T isNotEqualTo(String value) {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setDoesNotEqual(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setSelect(selectFilter));
+ this.doesNotEqual = value;
+ return toFilter();
}
/**
@@ -648,9 +672,8 @@ public T isNotEqualTo(String value) {
* @return a filter with the newly defined condition
*/
public T isEmpty() {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setSelect(selectFilter));
+ this.isEmpty = true;
+ return toFilter();
}
/**
@@ -658,9 +681,8 @@ public T isEmpty() {
* @return a filter with the newly defined condition
*/
public T isNotEmpty() {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setSelect(selectFilter));
+ this.isNotEmpty = true;
+ return toFilter();
}
}
@@ -672,8 +694,24 @@ public T isNotEmpty() {
*/
public static final class StatusCondition extends Condition {
- private StatusCondition(String property, NotionPropertyFilterFactory factory) {
- super(property, factory);
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable String equals;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable String doesNotEqual;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isEmpty;
+
+ @SuppressWarnings("unused")
+ @JsonProperty
+ private @Nullable Boolean isNotEmpty;
+
+ private StatusCondition(String property, PropertyFilterFactory factory) {
+ super("status", property, factory);
}
/**
@@ -682,9 +720,8 @@ private StatusCondition(String property, NotionPropertyFilterFactory factory)
* @return a filter with the newly defined condition
*/
public T isEqualTo(String value) {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setEquals(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setStatus(statusFilter));
+ this.equals = value;
+ return toFilter();
}
/**
@@ -694,9 +731,8 @@ public T isEqualTo(String value) {
* @return a filter with the newly defined condition
*/
public T isNotEqualTo(String value) {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setDoesNotEqual(value);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setStatus(statusFilter));
+ this.doesNotEqual = value;
+ return toFilter();
}
/**
@@ -704,9 +740,8 @@ public T isNotEqualTo(String value) {
* @return a filter with the newly defined condition
*/
public T isEmpty() {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setStatus(statusFilter));
+ this.isEmpty = true;
+ return toFilter();
}
/**
@@ -714,9 +749,8 @@ public T isEmpty() {
* @return a filter with the newly defined condition
*/
public T isNotEmpty() {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setNotEmpty(true);
- return toFilter(notionPropertyFilter -> notionPropertyFilter.setStatus(statusFilter));
+ this.isNotEmpty = true;
+ return toFilter();
}
}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
index 8c1455d8..04e0df37 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
@@ -15,22 +15,19 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.NotionClient;
-import notion.api.v1.http.JavaNetHttpClient;
-import notion.api.v1.logging.Slf4jLogger;
-import notion.api.v1.model.databases.QueryResults;
-import notion.api.v1.model.databases.query.filter.QueryTopLevelFilter;
-import notion.api.v1.model.databases.query.sort.QuerySort;
-import notion.api.v1.model.pages.Page;
-import notion.api.v1.model.pages.PageProperty;
-import notion.api.v1.model.pages.PageProperty.RichText;
-import notion.api.v1.request.databases.QueryDatabaseRequest;
import org.jspecify.annotations.Nullable;
+import org.springframework.batch.extensions.notion.PageProperty.RichTextProperty;
+import org.springframework.batch.extensions.notion.PageProperty.TitleProperty;
import org.springframework.batch.extensions.notion.mapping.PropertyMapper;
import org.springframework.batch.infrastructure.item.ExecutionContext;
import org.springframework.batch.infrastructure.item.ItemReader;
import org.springframework.batch.infrastructure.item.data.AbstractPaginatedDataItemReader;
+import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
+import org.springframework.web.client.ApiVersionInserter;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.support.RestClientAdapter;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import java.util.Collections;
import java.util.Iterator;
@@ -39,7 +36,6 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* Restartable {@link ItemReader} that reads entries from a Notion database via a paging
@@ -71,11 +67,11 @@ public class NotionDatabaseItemReader extends AbstractPaginatedDataItemReader
private String baseUrl = DEFAULT_BASE_URL;
- private @Nullable QueryTopLevelFilter filter;
+ private @Nullable Filter filter;
- private @Nullable List sorts;
+ private Sort[] sorts = new Sort[0];
- private @Nullable NotionClient client;
+ private @Nullable NotionDatabaseService service;
private boolean hasMore;
@@ -117,7 +113,7 @@ public void setBaseUrl(String baseUrl) {
* @see Filter#where(Filter)
*/
public void setFilter(Filter filter) {
- this.filter = filter.toQueryTopLevelFilter();
+ this.filter = filter;
}
/**
@@ -130,7 +126,7 @@ public void setFilter(Filter filter) {
* @see Sort#by(Sort.Timestamp)
*/
public void setSorts(Sort... sorts) {
- this.sorts = Stream.of(sorts).map(Sort::toQuerySort).toList();
+ this.sorts = sorts;
}
/**
@@ -151,10 +147,15 @@ public void setPageSize(int pageSize) {
*/
@Override
protected void doOpen() {
- client = new NotionClient(token);
- client.setHttpClient(new JavaNetHttpClient());
- client.setLogger(new Slf4jLogger());
- client.setBaseUrl(baseUrl);
+ RestClient restClient = RestClient.builder()
+ .baseUrl(baseUrl)
+ .apiVersionInserter(ApiVersionInserter.useHeader("Notion-Version"))
+ .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
+ .build();
+
+ RestClientAdapter adapter = RestClientAdapter.create(restClient);
+ HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
+ service = factory.createClient(NotionDatabaseService.class);
hasMore = true;
}
@@ -168,53 +169,47 @@ protected Iterator doPageRead() {
return Collections.emptyIterator();
}
- QueryDatabaseRequest request = new QueryDatabaseRequest(databaseId);
- request.setFilter(filter);
- request.setSorts(sorts);
- request.setStartCursor(nextCursor);
- request.setPageSize(pageSize);
+ QueryRequest request = new QueryRequest(pageSize, nextCursor, filter, sorts);
@SuppressWarnings("DataFlowIssue")
- QueryResults queryResults = client.queryDatabase(request);
+ QueryResult result = service.query(databaseId, request);
- hasMore = queryResults.getHasMore();
- nextCursor = queryResults.getNextCursor();
+ hasMore = result.hasMore();
+ nextCursor = result.nextCursor();
- return queryResults.getResults()
+ return result.results()
.stream()
.map(NotionDatabaseItemReader::getProperties)
.map(propertyMapper::map)
.iterator();
}
- private static Map getProperties(Page element) {
- return element.getProperties()
+ private static Map getProperties(Page page) {
+ return page.properties()
.entrySet()
.stream()
.collect(Collectors.toUnmodifiableMap(Entry::getKey, entry -> getPropertyValue(entry.getValue())));
}
private static String getPropertyValue(PageProperty property) {
- return switch (property.getType()) {
- case RichText -> getPlainText(property.getRichText());
- case Title -> getPlainText(property.getTitle());
- default -> throw new IllegalArgumentException("Unsupported type: " + property.getType());
- };
+ if (property instanceof RichTextProperty p) {
+ return getPlainText(p.richText());
+ }
+ if (property instanceof TitleProperty p) {
+ return getPlainText(p.title());
+ }
+ throw new IllegalArgumentException("Unsupported type: " + property.getClass());
}
private static String getPlainText(List texts) {
- return texts.isEmpty() ? "" : texts.get(0).getPlainText();
+ return texts.isEmpty() ? "" : texts.get(0).plainText();
}
/**
* {@inheritDoc}
*/
- @SuppressWarnings("DataFlowIssue")
@Override
protected void doClose() {
- client.close();
- client = null;
-
hasMore = false;
}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseService.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseService.java
new file mode 100644
index 00000000..1e0e88d8
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.service.annotation.HttpExchange;
+import org.springframework.web.service.annotation.PostExchange;
+
+@HttpExchange(url = "/databases", version = "2022-06-28", accept = MediaType.APPLICATION_JSON_VALUE)
+interface NotionDatabaseService {
+
+ @PostExchange("/{databaseId}/query")
+ QueryResult query(@PathVariable String databaseId, @RequestBody QueryRequest request);
+
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Page.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Page.java
new file mode 100644
index 00000000..085250d6
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Page.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import java.util.Map;
+
+record Page(Map properties) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/PageProperty.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/PageProperty.java
new file mode 100644
index 00000000..9d8ece29
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/PageProperty.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonTypeInfo(use = Id.NAME, property = "type")
+@JsonSubTypes({ //
+ @Type(name = "rich_text", value = PageProperty.RichTextProperty.class),
+ @Type(name = "title", value = PageProperty.TitleProperty.class) //
+})
+interface PageProperty {
+
+ @JsonNaming(SnakeCaseStrategy.class)
+ record RichTextProperty(List richText) implements PageProperty {
+ }
+
+ record TitleProperty(List title) implements PageProperty {
+ }
+
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryRequest.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryRequest.java
new file mode 100644
index 00000000..da2ff997
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonNaming(SnakeCaseStrategy.class)
+@JsonInclude(Include.NON_EMPTY)
+record QueryRequest(int pageSize, @Nullable String startCursor, @Nullable Filter filter, Sort... sorts) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryResult.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryResult.java
new file mode 100644
index 00000000..4ccceb0f
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryResult.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonNaming(SnakeCaseStrategy.class)
+record QueryResult(List results, String nextCursor, boolean hasMore) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/RichText.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/RichText.java
new file mode 100644
index 00000000..58293257
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/RichText.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import tools.jackson.databind.PropertyNamingStrategies;
+import tools.jackson.databind.annotation.JsonNaming;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+record RichText(String plainText) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java
index d42a51bb..e285e0a9 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java
@@ -15,9 +15,10 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.model.databases.query.sort.QuerySort;
-import notion.api.v1.model.databases.query.sort.QuerySortDirection;
-import notion.api.v1.model.databases.query.sort.QuerySortTimestamp;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import tools.jackson.databind.EnumNamingStrategies;
+import tools.jackson.databind.EnumNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.EnumNaming;
import java.util.Objects;
@@ -81,66 +82,48 @@ public static Sort by(Timestamp timestamp) {
/**
* Timestamps associated with database entries.
*/
+ @EnumNaming(SnakeCaseStrategy.class)
public enum Timestamp {
/**
* The time the entry was created.
*/
- CREATED_TIME(QuerySortTimestamp.CreatedTime),
+ CREATED_TIME,
/**
* The time the entry was last edited.
*/
- LAST_EDITED_TIME(QuerySortTimestamp.LastEditedTime);
-
- private final QuerySortTimestamp querySortTimestamp;
-
- Timestamp(QuerySortTimestamp querySortTimestamp) {
- this.querySortTimestamp = querySortTimestamp;
- }
-
- private QuerySortTimestamp getQuerySortTimestamp() {
- return querySortTimestamp;
- }
+ LAST_EDITED_TIME;
}
/**
* Sort directions.
*/
+ @EnumNaming(SnakeCaseStrategy.class)
public enum Direction {
/**
* Ascending direction.
*/
- ASCENDING(QuerySortDirection.Ascending),
+ ASCENDING,
/**
* Descending direction.
*/
- DESCENDING(QuerySortDirection.Descending);
-
- private final QuerySortDirection querySortDirection;
-
- Direction(QuerySortDirection querySortDirection) {
- this.querySortDirection = querySortDirection;
- }
-
- private QuerySortDirection getQuerySortDirection() {
- return querySortDirection;
- }
+ DESCENDING;
}
private Sort() {
}
- abstract QuerySort toQuerySort();
-
private static final class PropertySort extends Sort {
+ @JsonProperty
private final String property;
+ @JsonProperty
private final Direction direction;
private PropertySort(String property, Direction direction) {
@@ -148,11 +131,6 @@ private PropertySort(String property, Direction direction) {
this.direction = Objects.requireNonNull(direction);
}
- @Override
- QuerySort toQuerySort() {
- return new QuerySort(property, null, direction.getQuerySortDirection());
- }
-
@Override
public String toString() {
return "%s: %s".formatted(property, direction);
@@ -162,8 +140,10 @@ public String toString() {
private static final class TimestampSort extends Sort {
+ @JsonProperty
private final Timestamp timestamp;
+ @JsonProperty
private final Direction direction;
private TimestampSort(Timestamp timestamp, Direction direction) {
@@ -171,11 +151,6 @@ private TimestampSort(Timestamp timestamp, Direction direction) {
this.direction = Objects.requireNonNull(direction);
}
- @Override
- QuerySort toQuerySort() {
- return new QuerySort(null, timestamp.getQuerySortTimestamp(), direction.getQuerySortDirection());
- }
-
@Override
public String toString() {
return "%s: %s".formatted(timestamp, direction);
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java
index 0b8e88c4..37204625 100644
--- a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java
@@ -15,25 +15,16 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.model.databases.query.filter.CompoundFilter;
-import notion.api.v1.model.databases.query.filter.PropertyFilter;
-import notion.api.v1.model.databases.query.filter.QueryTopLevelFilter;
-import notion.api.v1.model.databases.query.filter.condition.CheckboxFilter;
-import notion.api.v1.model.databases.query.filter.condition.FilesFilter;
-import notion.api.v1.model.databases.query.filter.condition.MultiSelectFilter;
-import notion.api.v1.model.databases.query.filter.condition.NumberFilter;
-import notion.api.v1.model.databases.query.filter.condition.SelectFilter;
-import notion.api.v1.model.databases.query.filter.condition.StatusFilter;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.FieldSource;
+import tools.jackson.databind.json.JsonMapper;
import java.util.List;
-import java.util.function.Supplier;
import java.util.stream.Stream;
-import static org.assertj.core.api.BDDAssertions.then;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
import static org.springframework.batch.extensions.notion.Filter.where;
/**
@@ -41,244 +32,272 @@
*/
class FilterTests {
+ // Test cases from https://developers.notion.com/reference/post-database-query-filter
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
@ParameterizedTest
@FieldSource({ "PROPERTY_FILTERS", "COMPOUND_FILTERS", "NESTED_FILTERS" })
- void toQueryTopLevelFilter(Filter underTest, QueryTopLevelFilter expected) {
+ void toJson(Filter underTest, String expected) throws Exception {
// WHEN
- QueryTopLevelFilter result = underTest.toQueryTopLevelFilter();
+ String result = jsonMapper.writeValueAsString(underTest);
// THEN
- then(result).usingRecursiveComparison().isEqualTo(expected);
+ assertEquals(expected, result, true);
}
static final List CHECKBOX_FILTERS = Stream.of(true, false)
.flatMap(value -> Stream.of( //
- arguments( //
- where().checkbox("property").isEqualTo(value), //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(value);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- })),
- arguments( //
- where().checkbox("property").isNotEqualTo(value), //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setDoesNotEqual(value);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }))))
+ arguments(where().checkbox("Task completed").isEqualTo(value), """
+ {
+ "filter": {
+ "property": "Task completed",
+ "checkbox": {
+ "equals": %s
+ }
+ }
+ }
+ """.formatted(value)), //
+ arguments(where().checkbox("Task completed").isNotEqualTo(value), """
+ {
+ "filter": {
+ "property": "Task completed",
+ "checkbox": {
+ "does_not_equal": %s
+ }
+ }
+ }
+ """.formatted(value))))
.toList();
static final List FILES_FILTERS = List.of( //
- arguments( //
- where().files("property").isEmpty(), //
- supply(() -> {
- FilesFilter filesFilter = new FilesFilter();
- filesFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setFile(filesFilter);
- return propertyFilter;
- })),
- arguments( //
- where().files("property").isNotEmpty(), //
- supply(() -> {
- FilesFilter filesFilter = new FilesFilter();
- filesFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setFile(filesFilter);
- return propertyFilter;
- })));
+ arguments(where().files("Blueprint").isEmpty(), """
+ {
+ "filter": {
+ "property": "Blueprint",
+ "files": {
+ "is_empty": true
+ }
+ }
+ }
+ """), //
+ arguments(where().files("Blueprint").isNotEmpty(), """
+ {
+ "filter": {
+ "property": "Blueprint",
+ "files": {
+ "is_not_empty": true
+ }
+ }
+ }
+ """));
static final List MULTI_SELECT_FILTERS = List.of( //
- arguments( //
- where().multiSelect("property").contains("value"), //
- supply(() -> {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setContains("value");
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setMultiSelect(multiSelectFilter);
- return propertyFilter;
- })),
- arguments( //
- where().multiSelect("property").doesNotContain("value"), //
- supply(() -> {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setDoesNotContain("value");
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setMultiSelect(multiSelectFilter);
- return propertyFilter;
- })),
- arguments( //
- where().multiSelect("property").isEmpty(), //
- supply(() -> {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setMultiSelect(multiSelectFilter);
- return propertyFilter;
- })),
- arguments( //
- where().multiSelect("property").isNotEmpty(), //
- supply(() -> {
- MultiSelectFilter multiSelectFilter = new MultiSelectFilter();
- multiSelectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setMultiSelect(multiSelectFilter);
- return propertyFilter;
- })));
+ arguments(where().multiSelect("Programming language").contains("TypeScript"), """
+ {
+ "filter": {
+ "property": "Programming language",
+ "multi_select": {
+ "contains": "TypeScript"
+ }
+ }
+ }
+ """), //
+ arguments(where().multiSelect("Programming language").doesNotContain("TypeScript"), """
+ {
+ "filter": {
+ "property": "Programming language",
+ "multi_select": {
+ "does_not_contain": "TypeScript"
+ }
+ }
+ }
+ """), //
+ arguments(where().multiSelect("Programming language").isEmpty(), """
+ {
+ "filter": {
+ "property": "Programming language",
+ "multi_select": {
+ "is_empty": true
+ }
+ }
+ }
+ """), //
+ arguments(where().multiSelect("Programming language").isNotEmpty(), """
+ {
+ "filter": {
+ "property": "Programming language",
+ "multi_select": {
+ "is_not_empty": true
+ }
+ }
+ }
+ """));
static final List NUMBER_FILTERS = List.of( //
- arguments( //
- where().number("property").isEqualTo(42), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setEquals(42);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isNotEqualTo(42), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setDoesNotEqual(42);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isGreaterThan(42), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setGreaterThan(42);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isGreaterThanOrEqualTo(42), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setGreaterThanOrEqualTo(42);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isLessThan(42), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setLessThan(42);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isLessThanOrEqualTo(42), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setLessThanOrEqualTo(42);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isEmpty(), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })),
- arguments( //
- where().number("property").isNotEmpty(), //
- supply(() -> {
- NumberFilter numberFilter = new NumberFilter();
- numberFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setNumber(numberFilter);
- return propertyFilter;
- })));
+ arguments(where().number("Estimated working days").isEqualTo(42), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "equals": 42
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isNotEqualTo(42), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "does_not_equal": 42
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isGreaterThan(42), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "greater_than": 42
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isGreaterThanOrEqualTo(42), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "greater_than_or_equal_to": 42
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isLessThan(42), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "less_than": 42
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isLessThanOrEqualTo(42), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "less_than_or_equal_to": 42
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isEmpty(), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "is_empty": true
+ }
+ }
+ }
+ """), //
+ arguments(where().number("Estimated working days").isNotEmpty(), """
+ {
+ "filter": {
+ "property": "Estimated working days",
+ "number": {
+ "is_not_empty": true
+ }
+ }
+ }
+ """));
static final List SELECT_FILTERS = List.of( //
- arguments( //
- where().select("property").isEqualTo("value"), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEquals("value");
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })),
- arguments( //
- where().select("property").isNotEqualTo("value"), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setDoesNotEqual("value");
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })),
- arguments( //
- where().select("property").isEmpty(), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })),
- arguments( //
- where().select("property").isNotEmpty(), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
+ arguments(where().select("Backend framework").isEqualTo("Spring"), """
+ {
+ "filter": {
+ "property": "Backend framework",
+ "select": {
+ "equals": "Spring"
+ }
+ }
+ }
+ """), //
+ arguments(where().select("Backend framework").isNotEqualTo("Spring"), """
+ {
+ "filter": {
+ "property": "Backend framework",
+ "select": {
+ "does_not_equal": "Spring"
+ }
+ }
+ }
+ """), //
+ arguments(where().select("Backend framework").isEmpty(), """
+ {
+ "filter": {
+ "property": "Backend framework",
+ "select": {
+ "is_empty": true
+ }
+ }
+ }
+ """), //
+ arguments(where().select("Backend framework").isNotEmpty(), """
+ {
+ "filter": {
+ "property": "Backend framework",
+ "select": {
+ "is_not_empty": true
+ }
+ }
+ }
+ """));
static final List STATUS_FILTERS = List.of( //
- arguments( //
- where().status("property").isEqualTo("value"), //
- supply(() -> {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setEquals("value");
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setStatus(statusFilter);
- return propertyFilter;
- })),
- arguments( //
- where().status("property").isNotEqualTo("value"), //
- supply(() -> {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setDoesNotEqual("value");
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setStatus(statusFilter);
- return propertyFilter;
- })),
- arguments( //
- where().status("property").isEmpty(), //
- supply(() -> {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setStatus(statusFilter);
- return propertyFilter;
- })),
- arguments( //
- where().status("property").isNotEmpty(), //
- supply(() -> {
- StatusFilter statusFilter = new StatusFilter();
- statusFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setStatus(statusFilter);
- return propertyFilter;
- })));
+ arguments(where().status("Project status").isEqualTo("Not started"), """
+ {
+ "filter": {
+ "property": "Project status",
+ "status": {
+ "equals": "Not started"
+ }
+ }
+ }
+ """), //
+ arguments(where().status("Project status").isNotEqualTo("Not started"), """
+ {
+ "filter": {
+ "property": "Project status",
+ "status": {
+ "does_not_equal": "Not started"
+ }
+ }
+ }
+ """), //
+ arguments(where().status("Project status").isEmpty(), """
+ {
+ "filter": {
+ "property": "Project status",
+ "status": {
+ "is_empty": true
+ }
+ }
+ }
+ """), //
+ arguments(where().status("Project status").isNotEmpty(), """
+ {
+ "filter": {
+ "property": "Project status",
+ "status": {
+ "is_not_empty": true
+ }
+ }
+ }
+ """));
static final List PROPERTY_FILTERS = Stream.of( //
CHECKBOX_FILTERS, //
@@ -291,284 +310,246 @@ void toQueryTopLevelFilter(Filter underTest, QueryTopLevelFilter expected) {
.toList();
static final List AND_FILTERS = List.of( //
+ arguments(where().checkbox("Complete").isEqualTo(true).and().number("Days").isGreaterThan(10), """
+ {
+ "filter": {
+ "and": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Days",
+ "number": {
+ "greater_than": 10
+ }
+ }
+ ]
+ }
+ }
+ """),
+ arguments(where().checkbox("Complete").isEqualTo(true).and(where().number("Days").isGreaterThan(10)), """
+ {
+ "filter": {
+ "and": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Days",
+ "number": {
+ "greater_than": 10
+ }
+ }
+ ]
+ }
+ }
+ """),
arguments( // @formatter:off
- where().checkbox("active").isEqualTo(false)
- .and().select("another").isNotEmpty(), //
+ where().checkbox("Complete").isEqualTo(true)
+ .and(where().number("Days").isGreaterThan(10))
+ .and().checkbox("Archived").isNotEqualTo(false)
+ .and(where().select("Language").isEmpty()),
// @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setAnd(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })),
- arguments( // @formatter:off
- where().checkbox("active").isEqualTo(false)
- .and(where().select("another").isNotEmpty()),
- // @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setAnd(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })),
- arguments( // @formatter:off
- where().checkbox("active").isEqualTo(false)
- .and(where().select("another").isNotEmpty())
- .and().checkbox("one-more").isNotEqualTo(true)
- .and(where().select("another-more").isEmpty()),
- // @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setAnd(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setDoesNotEqual(true);
- PropertyFilter propertyFilter = new PropertyFilter("one-more");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another-more");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })));
+ """
+ {
+ "filter": {
+ "and": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Days",
+ "number": {
+ "greater_than": 10
+ }
+ },
+ {
+ "property": "Archived",
+ "checkbox": {
+ "does_not_equal": false
+ }
+ },
+ {
+ "property": "Language",
+ "select": {
+ "is_empty": true
+ }
+ }
+ ]
+ }
+ }
+ """));
static final List OR_FILTERS = List.of( //
+ arguments(where().checkbox("Complete").isEqualTo(true).or().number("Days").isGreaterThan(10), """
+ {
+ "filter": {
+ "or": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Days",
+ "number": {
+ "greater_than": 10
+ }
+ }
+ ]
+ }
+ }
+ """),
+ arguments(where().checkbox("Complete").isEqualTo(true).or(where().number("Days").isGreaterThan(10)), """
+ {
+ "filter": {
+ "or": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Days",
+ "number": {
+ "greater_than": 10
+ }
+ }
+ ]
+ }
+ }
+ """),
arguments( // @formatter:off
- where().checkbox("active").isEqualTo(false)
- .or().select("another").isNotEmpty(),
- // @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setOr(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })),
- arguments( // @formatter:off
- where().checkbox("active").isEqualTo(false)
- .or(where().select("another").isNotEmpty()),
- // @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setOr(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })),
- arguments( // @formatter:off
- where().checkbox("active").isEqualTo(false)
- .or(where().select("another").isNotEmpty())
- .or().checkbox("one-more").isNotEqualTo(true)
- .or(where().select("another-more").isEmpty()),
+ where().checkbox("Complete").isEqualTo(true)
+ .or(where().number("Days").isGreaterThan(10))
+ .or().checkbox("Archived").isNotEqualTo(false)
+ .or(where().select("Language").isEmpty()),
// @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setOr(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setDoesNotEqual(true);
- PropertyFilter propertyFilter = new PropertyFilter("one-more");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another-more");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })));
+ """
+ {
+ "filter": {
+ "or": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Days",
+ "number": {
+ "greater_than": 10
+ }
+ },
+ {
+ "property": "Archived",
+ "checkbox": {
+ "does_not_equal": false
+ }
+ },
+ {
+ "property": "Language",
+ "select": {
+ "is_empty": true
+ }
+ }
+ ]
+ }
+ }
+ """));
static final List COMPOUND_FILTERS = Stream.of(AND_FILTERS, OR_FILTERS).flatMap(List::stream).toList();
static final List NESTED_FILTERS = List.of( //
+ arguments(where(where().checkbox("Task completed").isEqualTo(true)), """
+ {
+ "filter": {
+ "property": "Task completed",
+ "checkbox": {
+ "equals": true
+ }
+ }
+ }
+ """),
arguments( // @formatter:off
- where(where().checkbox("active").isEqualTo(true)),
+ where().checkbox("Complete").isEqualTo(true)
+ .and(where().select("Language").isEmpty().or().select("Language").isEqualTo("Java")),
// @formatter:on
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(true);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- })),
+ """
+ {
+ "filter": {
+ "and": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "or": [
+ {
+ "property": "Language",
+ "select": {
+ "is_empty": true
+ }
+ },
+ {
+ "property": "Language",
+ "select": {
+ "equals": "Java"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ """),
arguments( // @formatter:off
- where().checkbox("active").isEqualTo(true)
- .and(where().select("another").isEmpty().or().select("another").isEqualTo("value")),
+ where(where().checkbox("Complete").isEqualTo(true)
+ .or().select("Language").isNotEmpty())
+ .and().select("Backend framework").isEmpty(),
// @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setAnd(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(true);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- CompoundFilter innerFilter = new CompoundFilter();
-
- innerFilter.setOr(List.of( //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEquals("value");
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return innerFilter;
- })));
-
- return compoundFilter;
- })),
- arguments( // @formatter:off
- where(where().checkbox("active").isEqualTo(false)
- .or().select("another").isNotEmpty())
- .and().select("one-more").isEmpty(),
- // @formatter:on
- supply(() -> {
- CompoundFilter compoundFilter = new CompoundFilter();
-
- compoundFilter.setAnd(List.of( //
- supply(() -> {
- CompoundFilter innerFilter = new CompoundFilter();
-
- innerFilter.setOr(List.of( //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(false);
- PropertyFilter propertyFilter = new PropertyFilter("active");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setNotEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("another");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return innerFilter;
- }), //
- supply(() -> {
- SelectFilter selectFilter = new SelectFilter();
- selectFilter.setEmpty(true);
- PropertyFilter propertyFilter = new PropertyFilter("one-more");
- propertyFilter.setSelect(selectFilter);
- return propertyFilter;
- })));
-
- return compoundFilter;
- })));
-
- private static T supply(Supplier supplier) {
- return supplier.get();
- }
+ """
+ {
+ "filter": {
+ "and": [
+ {
+ "or": [
+ {
+ "property": "Complete",
+ "checkbox": {
+ "equals": true
+ }
+ },
+ {
+ "property": "Language",
+ "select": {
+ "is_not_empty": true
+ }
+ }
+ ]
+ },
+ {
+ "property": "Backend framework",
+ "select": {
+ "is_empty": true
+ }
+ }
+ ]
+ }
+ }
+ """));
}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/NotionJvmSdkTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/NotionJvmSdkTests.java
deleted file mode 100644
index 780a2f74..00000000
--- a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/NotionJvmSdkTests.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2024-2025 the original author or authors.
- *
- * 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
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.extensions.notion;
-
-import com.tngtech.archunit.base.DescribedPredicate;
-import com.tngtech.archunit.core.domain.JavaClass;
-import com.tngtech.archunit.core.domain.JavaClasses;
-import com.tngtech.archunit.junit.AnalyzeClasses;
-import com.tngtech.archunit.junit.ArchTest;
-import com.tngtech.archunit.lang.ArchRule;
-
-import static com.tngtech.archunit.base.DescribedPredicate.anyElementThat;
-import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
-
-/**
- * @author Stefano Cordio
- */
-@AnalyzeClasses(packagesOf = NotionDatabaseItemReader.class)
-class NotionJvmSdkTests {
-
- private static final DescribedPredicate RESIDE_IN_NOTION_JVM_SDK_PACKAGE = //
- resideInAPackage("notion.api..");
-
- @ArchTest
- void library_types_should_not_be_exposed(JavaClasses classes) {
- // @formatter:off
- ArchRule rule = methods()
- .that().arePublic().or().areProtected()
- .should().notHaveRawReturnType(RESIDE_IN_NOTION_JVM_SDK_PACKAGE)
- .andShould().notHaveRawParameterTypes(anyElementThat(RESIDE_IN_NOTION_JVM_SDK_PACKAGE));
- // @formatter:on
- rule.check(classes);
- }
-
-}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/PagePropertyTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/PagePropertyTests.java
new file mode 100644
index 00000000..32d23404
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/PagePropertyTests.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.batch.extensions.notion.PageProperty.RichTextProperty;
+import org.springframework.batch.extensions.notion.PageProperty.TitleProperty;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Stefano Cordio
+ */
+class PagePropertyTests {
+
+ // Test cases from https://developers.notion.com/reference/page-property-values
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @Test
+ void richTextProperty() {
+ // GIVEN
+ String json = """
+ {
+ "Description": {
+ "id": "HbZT",
+ "type": "rich_text",
+ "rich_text": [
+ {
+ "type": "text",
+ "text": {
+ "content": "There is some ",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "There is some ",
+ "href": null
+ },
+ {
+ "type": "text",
+ "text": {
+ "content": "text",
+ "link": null
+ },
+ "annotations": {
+ "bold": true,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "text",
+ "href": null
+ },
+ {
+ "type": "text",
+ "text": {
+ "content": " in this property!",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": " in this property!",
+ "href": null
+ }
+ ]
+ }
+ }
+ """;
+ Map expected = Map.of("Description", new RichTextProperty(List.of( //
+ new RichText("There is some "), //
+ new RichText("text"), //
+ new RichText(" in this property!"))));
+ // WHEN
+ Map result = jsonMapper.readValue(json, new TypeReference<>() {
+ });
+ // THEN
+ then(result).isEqualTo(expected);
+ }
+
+ @Test
+ void titleProperty() {
+ // GIVEN
+ String json = """
+ {
+ "Title": {
+ "id": "title",
+ "type": "title",
+ "title": [
+ {
+ "type": "text",
+ "text": {
+ "content": "A better title for the page",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "This is also not done",
+ "href": null
+ }
+ ]
+ }
+ }
+ """;
+ Map expected = Map.of("Title",
+ new TitleProperty(List.of(new RichText("This is also not done"))));
+ // WHEN
+ Map result = jsonMapper.readValue(json, new TypeReference<>() {
+ });
+ // THEN
+ then(result).isEqualTo(expected);
+ }
+
+}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/QueryRequestTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/QueryRequestTests.java
new file mode 100644
index 00000000..764f3700
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/QueryRequestTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.FieldSource;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.util.List;
+
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
+import static org.springframework.batch.extensions.notion.Sort.Timestamp.CREATED_TIME;
+
+/**
+ * @author Stefano Cordio
+ */
+class QueryRequestTests {
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @ParameterizedTest
+ @FieldSource
+ void toJson(QueryRequest underTest, String expected) throws Exception {
+ // WHEN
+ String result = jsonMapper.writeValueAsString(underTest);
+ // THEN
+ assertEquals(expected, result, true);
+ }
+
+ static List toJson = List.of( //
+ arguments(new QueryRequest(42, null, null), """
+ {
+ "page_size" : 42
+ }
+ """), //
+ arguments(new QueryRequest(42, "cursor", null), """
+ {
+ "page_size" : 42,
+ "start_cursor" : "cursor"
+ }
+ """), //
+ arguments(new QueryRequest(42, null, null, Sort.by("property")), """
+ {
+ "page_size" : 42,
+ "sorts" : [
+ {
+ "direction" : "ascending",
+ "property" : "property"
+ }
+ ]
+ }
+ """), //
+ arguments(new QueryRequest(42, null, null, Sort.by("property"), Sort.by(CREATED_TIME)), """
+ {
+ "page_size" : 42,
+ "sorts" : [
+ {
+ "property" : "property",
+ "direction" : "ascending"
+ },
+ {
+ "timestamp" : "created_time",
+ "direction" : "ascending"
+ }
+ ]
+ }
+ """));
+
+}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/RichTextTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/RichTextTests.java
new file mode 100644
index 00000000..c68e6294
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/RichTextTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import org.junit.jupiter.api.Test;
+import tools.jackson.databind.json.JsonMapper;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+/**
+ * @author Stefano Cordio
+ */
+class RichTextTests {
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @Test
+ void fromJson() {
+ // GIVEN
+ String json = """
+ {
+ "type": "text",
+ "text": {
+ "content": "Some words ",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "Some words ",
+ "href": null
+ }
+ """;
+ RichText expected = new RichText("Some words ");
+ // WHEN
+ RichText result = jsonMapper.readValue(json, RichText.class);
+ // THEN
+ then(result).isEqualTo(expected);
+ }
+
+}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java
index fbd387b7..6479d3f9 100644
--- a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java
@@ -15,22 +15,16 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.model.databases.query.sort.QuerySort;
-import notion.api.v1.model.databases.query.sort.QuerySortDirection;
-import notion.api.v1.model.databases.query.sort.QuerySortTimestamp;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.FieldSource;
+import tools.jackson.databind.json.JsonMapper;
import java.util.List;
-import static notion.api.v1.model.databases.query.sort.QuerySortDirection.Ascending;
-import static notion.api.v1.model.databases.query.sort.QuerySortDirection.Descending;
-import static notion.api.v1.model.databases.query.sort.QuerySortTimestamp.CreatedTime;
-import static notion.api.v1.model.databases.query.sort.QuerySortTimestamp.LastEditedTime;
-import static org.assertj.core.api.BDDAssertions.from;
import static org.assertj.core.api.BDDAssertions.then;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
import static org.springframework.batch.extensions.notion.Sort.Direction.ASCENDING;
import static org.springframework.batch.extensions.notion.Sort.Direction.DESCENDING;
import static org.springframework.batch.extensions.notion.Sort.Timestamp.CREATED_TIME;
@@ -41,28 +35,72 @@
*/
class SortTests {
+ private final JsonMapper jsonMapper = new JsonMapper();
+
@ParameterizedTest
@FieldSource
- void toQuerySort(Sort underTest, String property, QuerySortTimestamp timestamp, QuerySortDirection direction) {
+ void toJson(Sort underTest, String expected) throws Exception {
// WHEN
- QuerySort result = underTest.toQuerySort();
+ String result = jsonMapper.writeValueAsString(underTest);
// THEN
- then(result) //
- .returns(direction, from(QuerySort::getDirection))
- .returns(property, from(QuerySort::getProperty))
- .returns(timestamp, from(QuerySort::getTimestamp));
+ assertEquals(expected, result, true);
}
- static List toQuerySort = List.of( //
- arguments(Sort.by("property"), "property", null, Ascending),
- arguments(Sort.by("property", ASCENDING), "property", null, Ascending),
- arguments(Sort.by("property", DESCENDING), "property", null, Descending),
- arguments(Sort.by(CREATED_TIME), null, CreatedTime, Ascending),
- arguments(Sort.by(CREATED_TIME, ASCENDING), null, CreatedTime, Ascending),
- arguments(Sort.by(CREATED_TIME, DESCENDING), null, CreatedTime, Descending),
- arguments(Sort.by(LAST_EDITED_TIME), null, LastEditedTime, Ascending),
- arguments(Sort.by(LAST_EDITED_TIME, ASCENDING), null, LastEditedTime, Ascending),
- arguments(Sort.by(LAST_EDITED_TIME, DESCENDING), null, LastEditedTime, Descending));
+ static List toJson = List.of( //
+ arguments(Sort.by("property"), """
+ {
+ "property" : "property",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by("property", ASCENDING), """
+ {
+ "property" : "property",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by("property", DESCENDING), """
+ {
+ "property" : "property",
+ "direction" : "descending"
+ }
+ """), //
+ arguments(Sort.by(CREATED_TIME), """
+ {
+ "timestamp" : "created_time",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by(CREATED_TIME, ASCENDING), """
+ {
+ "timestamp" : "created_time",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by(CREATED_TIME, DESCENDING), """
+ {
+ "timestamp" : "created_time",
+ "direction" : "descending"
+ }
+ """), //
+ arguments(Sort.by(LAST_EDITED_TIME), """
+ {
+ "timestamp" : "last_edited_time",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by(LAST_EDITED_TIME, ASCENDING), """
+ {
+ "timestamp" : "last_edited_time",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by(LAST_EDITED_TIME, DESCENDING), """
+ {
+ "timestamp" : "last_edited_time",
+ "direction" : "descending"
+ }
+ """));
@ParameterizedTest
@FieldSource