Initial Commit
Migrating the existing structure from the following location: https://github.com/spring-cloud-stream-app-starters/stream-applications/tree/restructuring
This commit is contained in:
30
consumer/cassandra-consumer/.gitignore
vendored
Normal file
30
consumer/cassandra-consumer/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
0
consumer/cassandra-consumer/.toDelete
Normal file
0
consumer/cassandra-consumer/.toDelete
Normal file
78
consumer/cassandra-consumer/pom.xml
Normal file
78
consumer/cassandra-consumer/pom.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cassandra-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>cassandra-consumer</name>
|
||||
<description>Cassandra Consumer</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<springIntegrationCassandara.version>0.8.0.BUILD-SNAPSHOT</springIntegrationCassandara.version>
|
||||
<cassandra-unit-spring.version>4.3.1.0</cassandra-unit-spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-cassandra</artifactId>
|
||||
<version>${springIntegrationCassandara.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cassandraunit</groupId>
|
||||
<artifactId>cassandra-unit-spring</artifactId>
|
||||
<version>${cassandra-unit-spring.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.addthis.metrics</groupId>
|
||||
<artifactId>reporter-config3</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.consumer.cassandra.query.InsertQueryColumnNameExtractor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.cassandra.core.InsertOptions;
|
||||
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
|
||||
import org.springframework.data.cassandra.core.UpdateOptions;
|
||||
import org.springframework.data.cassandra.core.WriteResult;
|
||||
import org.springframework.data.cassandra.core.cql.WriteOptions;
|
||||
import org.springframework.integration.cassandra.outbound.CassandraMessageHandler;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlowBuilder;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
|
||||
import org.springframework.integration.transformer.AbstractPayloadTransformer;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||
import org.springframework.cloud.fn.consumer.cassandra.query.ColumnNameExtractor;
|
||||
import org.springframework.cloud.fn.consumer.cassandra.query.UpdateQueryColumnNameExtractor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Thomas Risberg
|
||||
* @author Ashu Gairola
|
||||
* @author Akos Ratku
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CassandraConsumerProperties.class)
|
||||
public class CassandraConsumerConfiguration {
|
||||
|
||||
@Autowired
|
||||
private CassandraConsumerProperties cassandraSinkProperties;
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow cassandraConsumerFlow(MessageHandler cassandraSinkMessageHandler,
|
||||
ObjectMapper objectMapper) {
|
||||
IntegrationFlowBuilder integrationFlowBuilder =
|
||||
IntegrationFlows.from(CassandraConsumerFunction.class);
|
||||
if (StringUtils.hasText(this.cassandraSinkProperties.getIngestQuery())) {
|
||||
integrationFlowBuilder.transform(
|
||||
new PayloadToMatrixTransformer(objectMapper, this.cassandraSinkProperties.getIngestQuery(),
|
||||
CassandraMessageHandler.Type.UPDATE == this.cassandraSinkProperties.getQueryType()
|
||||
? new UpdateQueryColumnNameExtractor()
|
||||
: new InsertQueryColumnNameExtractor()));
|
||||
}
|
||||
return integrationFlowBuilder
|
||||
.handle(cassandraSinkMessageHandler)
|
||||
.get();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageHandler cassandraSinkMessageHandler(ReactiveCassandraOperations cassandraOperations) {
|
||||
CassandraMessageHandler cassandraMessageHandler =
|
||||
this.cassandraSinkProperties.getQueryType() != null
|
||||
? new CassandraMessageHandler(cassandraOperations, this.cassandraSinkProperties.getQueryType())
|
||||
: new CassandraMessageHandler(cassandraOperations);
|
||||
cassandraMessageHandler.setProducesReply(true);
|
||||
cassandraMessageHandler.setAsync(true);
|
||||
if (this.cassandraSinkProperties.getConsistencyLevel() != null
|
||||
|| this.cassandraSinkProperties.getTtl() > 0) {
|
||||
|
||||
WriteOptions.WriteOptionsBuilder writeOptionsBuilder = WriteOptions.builder();
|
||||
|
||||
switch (this.cassandraSinkProperties.getQueryType()) {
|
||||
|
||||
case INSERT:
|
||||
writeOptionsBuilder = InsertOptions.builder();
|
||||
break;
|
||||
case UPDATE:
|
||||
writeOptionsBuilder = UpdateOptions.builder();
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.cassandraSinkProperties.getConsistencyLevel() != null) {
|
||||
writeOptionsBuilder.consistencyLevel(this.cassandraSinkProperties.getConsistencyLevel());
|
||||
}
|
||||
|
||||
if (this.cassandraSinkProperties.getTtl() > 0) {
|
||||
writeOptionsBuilder.ttl(this.cassandraSinkProperties.getTtl());
|
||||
}
|
||||
|
||||
cassandraMessageHandler.setWriteOptions(writeOptionsBuilder.build());
|
||||
}
|
||||
if (StringUtils.hasText(this.cassandraSinkProperties.getIngestQuery())) {
|
||||
cassandraMessageHandler.setIngestQuery(this.cassandraSinkProperties.getIngestQuery());
|
||||
}
|
||||
else if (this.cassandraSinkProperties.getStatementExpression() != null) {
|
||||
cassandraMessageHandler.setStatementExpression(this.cassandraSinkProperties.getStatementExpression());
|
||||
}
|
||||
return cassandraMessageHandler;
|
||||
}
|
||||
|
||||
private static boolean isUuid(String uuid) {
|
||||
if (uuid.length() == 36) {
|
||||
String[] parts = uuid.split("-");
|
||||
if (parts.length == 5) {
|
||||
return (parts[0].length() == 8) && (parts[1].length() == 4) &&
|
||||
(parts[2].length() == 4) && (parts[3].length() == 4) &&
|
||||
(parts[4].length() == 12);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static class PayloadToMatrixTransformer extends AbstractPayloadTransformer<Object, List<List<Object>>> {
|
||||
|
||||
private final Jackson2JsonObjectMapper jsonObjectMapper;
|
||||
|
||||
private final List<String> columns = new LinkedList<>();
|
||||
|
||||
private final ISO8601StdDateFormat dateFormat = new ISO8601StdDateFormat();
|
||||
|
||||
PayloadToMatrixTransformer(ObjectMapper objectMapper, String query, ColumnNameExtractor columnNameExtractor) {
|
||||
this.jsonObjectMapper = new Jackson2JsonObjectMapper(objectMapper);
|
||||
this.columns.addAll(columnNameExtractor.extract(query));
|
||||
this.jsonObjectMapper.getObjectMapper()
|
||||
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<List<Object>> transformPayload(Object payload) {
|
||||
if (payload instanceof List) {
|
||||
return (List<List<Object>>) payload;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
List<Map<String, Object>> model = this.jsonObjectMapper.fromJson(payload, List.class);
|
||||
List<List<Object>> data = new ArrayList<>(model.size());
|
||||
for (Map<String, Object> entity : model) {
|
||||
List<Object> row = new ArrayList<>(this.columns.size());
|
||||
for (String column : this.columns) {
|
||||
Object value = entity.get(column);
|
||||
if (value instanceof String) {
|
||||
String string = (String) value;
|
||||
if (this.dateFormat.looksLikeISO8601(string)) {
|
||||
synchronized (this.dateFormat) {
|
||||
value = new Date(this.dateFormat.parse(string).getTime()).toLocalDate();
|
||||
}
|
||||
}
|
||||
if (isUuid(string)) {
|
||||
value = UUID.fromString(string);
|
||||
}
|
||||
}
|
||||
row.add(value);
|
||||
}
|
||||
data.add(row);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException("Cannot parse json into matrix", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* We need this to provide visibility to the protected method.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class ISO8601StdDateFormat extends StdDateFormat {
|
||||
|
||||
@Override
|
||||
protected boolean looksLikeISO8601(String dateStr) {
|
||||
return super.looksLikeISO8601(dateStr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface CassandraConsumerFunction extends Function<Object, Mono<? extends WriteResult>> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.cassandra.outbound.CassandraMessageHandler;
|
||||
|
||||
import com.datastax.oss.driver.api.core.ConsistencyLevel;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
@ConfigurationProperties("cassandra")
|
||||
public class CassandraConsumerProperties {
|
||||
|
||||
/**
|
||||
* Time-to-live option of WriteOptions.
|
||||
*/
|
||||
private int ttl;
|
||||
|
||||
/**
|
||||
* QueryType for Cassandra Sink.
|
||||
*/
|
||||
private CassandraMessageHandler.Type queryType;
|
||||
|
||||
/**
|
||||
* Ingest Cassandra query.
|
||||
*/
|
||||
private String ingestQuery;
|
||||
|
||||
/**
|
||||
* Expression in Cassandra query DSL style.
|
||||
*/
|
||||
private Expression statementExpression;
|
||||
|
||||
/**
|
||||
* The consistency level for write operation.
|
||||
*/
|
||||
private ConsistencyLevel consistencyLevel;
|
||||
|
||||
public int getTtl() {
|
||||
return this.ttl;
|
||||
}
|
||||
|
||||
public void setTtl(int ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public CassandraMessageHandler.Type getQueryType() {
|
||||
return this.queryType;
|
||||
}
|
||||
|
||||
public void setQueryType(CassandraMessageHandler.Type queryType) {
|
||||
this.queryType = queryType;
|
||||
}
|
||||
|
||||
public String getIngestQuery() {
|
||||
return this.ingestQuery;
|
||||
}
|
||||
|
||||
public void setIngestQuery(String ingestQuery) {
|
||||
this.ingestQuery = ingestQuery;
|
||||
}
|
||||
|
||||
public Expression getStatementExpression() {
|
||||
return this.statementExpression;
|
||||
}
|
||||
|
||||
public void setStatementExpression(Expression statementExpression) {
|
||||
this.statementExpression = statementExpression;
|
||||
}
|
||||
|
||||
public ConsistencyLevel getConsistencyLevel() {
|
||||
return this.consistencyLevel;
|
||||
}
|
||||
|
||||
public void setConsistencyLevel(ConsistencyLevel consistencyLevel) {
|
||||
this.consistencyLevel = consistencyLevel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.cluster;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties;
|
||||
import org.springframework.boot.autoconfigure.cassandra.CqlSessionBuilderCustomizer;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScanPackages;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.cassandra.config.CqlSessionFactoryBean;
|
||||
import org.springframework.data.cassandra.core.ReactiveCassandraTemplate;
|
||||
import org.springframework.data.cassandra.core.cql.CqlTemplate;
|
||||
import org.springframework.data.cassandra.core.cql.ReactiveCqlOperations;
|
||||
import org.springframework.data.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlSession;
|
||||
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Thomas Risberg
|
||||
* @author Rob Hardt
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CassandraClusterProperties.class)
|
||||
@Import(CassandraAppClusterConfiguration.CassandraPackageRegistrar.class)
|
||||
public class CassandraAppClusterConfiguration {
|
||||
|
||||
@Bean
|
||||
public CqlSessionBuilderCustomizer clusterBuilderCustomizer(
|
||||
CassandraClusterProperties cassandraClusterProperties) {
|
||||
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
return builder ->
|
||||
map.from(cassandraClusterProperties::isSkipSslValidation)
|
||||
.whenTrue()
|
||||
.toCall(() -> {
|
||||
try {
|
||||
builder.withSslContext(TrustAllSSLContextFactory.getSslContext());
|
||||
}
|
||||
catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new BeanInitializationException(
|
||||
"Unable to configure a Cassandra cluster using SSL.", e);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty("cassandra.cluster.create-keyspace")
|
||||
public Object keyspaceCreator(CassandraProperties cassandraProperties, CqlSessionBuilder cqlSessionBuilder) {
|
||||
CreateKeyspaceSpecification createKeyspaceSpecification =
|
||||
CreateKeyspaceSpecification
|
||||
.createKeyspace(cassandraProperties.getKeyspaceName())
|
||||
.withSimpleReplication()
|
||||
.ifNotExists();
|
||||
|
||||
String createKeySpaceQuery = new CreateKeyspaceCqlGenerator(createKeyspaceSpecification).toCql();
|
||||
CqlSession systemSession =
|
||||
cqlSessionBuilder.withKeyspace(CqlSessionFactoryBean.CASSANDRA_SYSTEM_SESSION).build();
|
||||
|
||||
CqlTemplate template = new CqlTemplate(systemSession);
|
||||
template.execute(createKeySpaceQuery);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@DependsOn("keyspaceCreator")
|
||||
public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) {
|
||||
return cqlSessionBuilder.build();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty("cassandra.cluster.init-script")
|
||||
public Object keyspaceInitializer(CassandraClusterProperties cassandraClusterProperties,
|
||||
ReactiveCassandraTemplate reactiveCassandraTemplate) throws IOException {
|
||||
|
||||
String scripts =
|
||||
new Scanner(cassandraClusterProperties.getInitScript().getInputStream(),
|
||||
StandardCharsets.UTF_8.name())
|
||||
.useDelimiter("\\A")
|
||||
.next();
|
||||
|
||||
ReactiveCqlOperations reactiveCqlOperations =
|
||||
reactiveCassandraTemplate.getReactiveCqlOperations();
|
||||
|
||||
Flux.fromArray(StringUtils.delimitedListToStringArray(scripts, ";", "\r\n\f"))
|
||||
.filter(StringUtils::hasText) // an empty String after the last ';'
|
||||
.flatMap(script -> reactiveCqlOperations.execute(script + ";"))
|
||||
.blockLast();
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
static class CassandraPackageRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
Binder.get(this.environment)
|
||||
.bind("cassandra.cluster.entity-base-packages", String[].class)
|
||||
.map(Arrays::asList)
|
||||
.ifBound(packagesToScan -> EntityScanPackages.register(registry, packagesToScan));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.cluster;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Common properties for the cassandra modules.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Thomas Risberg
|
||||
* @author Rob Hardt
|
||||
*/
|
||||
@ConfigurationProperties("cassandra.cluster")
|
||||
public class CassandraClusterProperties {
|
||||
|
||||
/**
|
||||
* Flag to create (or not) keyspace on application startup.
|
||||
*/
|
||||
private boolean createKeyspace;
|
||||
|
||||
/**
|
||||
* Resource with CQL scripts (delimited by ';') to initialize keyspace schema.
|
||||
*/
|
||||
private Resource initScript;
|
||||
|
||||
/**
|
||||
* Flag to validate the Servers' SSL certs
|
||||
*/
|
||||
private boolean skipSslValidation;
|
||||
|
||||
/**
|
||||
* Base packages to scan for entities annotated with Table annotations.
|
||||
*/
|
||||
private String[] entityBasePackages = { };
|
||||
|
||||
|
||||
public void setCreateKeyspace(boolean createKeyspace) {
|
||||
this.createKeyspace = createKeyspace;
|
||||
}
|
||||
|
||||
public void setInitScript(Resource initScript) {
|
||||
this.initScript = initScript;
|
||||
}
|
||||
|
||||
public void setSkipSslValidation(boolean skipSslValidation) {
|
||||
this.skipSslValidation = skipSslValidation;
|
||||
}
|
||||
|
||||
public boolean isCreateKeyspace() {
|
||||
return this.createKeyspace;
|
||||
}
|
||||
|
||||
public Resource getInitScript() {
|
||||
return this.initScript;
|
||||
}
|
||||
|
||||
public boolean isSkipSslValidation() {
|
||||
return this.skipSslValidation;
|
||||
}
|
||||
|
||||
public String[] getEntityBasePackages() {
|
||||
return this.entityBasePackages;
|
||||
}
|
||||
|
||||
public void setEntityBasePackages(String[] entityBasePackages) {
|
||||
this.entityBasePackages = entityBasePackages;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.cluster;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
|
||||
/**
|
||||
* Helper to provide an SSL Context that does not validate
|
||||
* certificates presented in the SSL handshake.
|
||||
*
|
||||
* The usual caveats apply.
|
||||
*
|
||||
* @author Rob Hardt
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
class TrustAllSSLContextFactory {
|
||||
|
||||
static SSLContext getSslContext() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, trustAllCerts, new SecureRandom());
|
||||
return sc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Akos Ratku
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ColumnNameExtractor {
|
||||
|
||||
List<String> extract(String query);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.query;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Akos Ratku
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class InsertQueryColumnNameExtractor implements ColumnNameExtractor {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile(".+\\((.+)\\).+(?:values\\s*\\((.+)\\))");
|
||||
|
||||
@Override
|
||||
public List<String> extract(String query) {
|
||||
List<String> extractedColumns = new LinkedList<>();
|
||||
Matcher matcher = PATTERN.matcher(query);
|
||||
if (matcher.matches()) {
|
||||
String[] columns = StringUtils.delimitedListToStringArray(matcher.group(1), ",", " ");
|
||||
String[] params = StringUtils.delimitedListToStringArray(matcher.group(2), ",", " ");
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
String param = params[i];
|
||||
if (param.equals("?")) {
|
||||
extractedColumns.add(columns[i]);
|
||||
}
|
||||
else if (param.startsWith(":")) {
|
||||
extractedColumns.add(param.substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Invalid CQL insert query syntax: " + query);
|
||||
}
|
||||
return extractedColumns;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.query;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Akos Ratku
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class UpdateQueryColumnNameExtractor implements ColumnNameExtractor {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("(?i)(?<=set)(.*)(?=where)where(.*)");
|
||||
|
||||
@Override
|
||||
public List<String> extract(String query) {
|
||||
List<String> extractedColumns = new LinkedList<>();
|
||||
Matcher matcher = PATTERN.matcher(query);
|
||||
if (matcher.find()) {
|
||||
String[] settings = StringUtils.delimitedListToStringArray(matcher.group(1), ",", " ");
|
||||
String[] where = StringUtils.delimitedListToStringArray(matcher.group(2), ",", " ");
|
||||
readPairs(extractedColumns, settings);
|
||||
readPairs(extractedColumns, where);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Invalid CQL update query syntax: " + query);
|
||||
}
|
||||
return extractedColumns;
|
||||
}
|
||||
|
||||
protected void readPairs(List<String> extractedColumns, String[] settings) {
|
||||
for (String setting : settings) {
|
||||
String[] columnValuePair = StringUtils.delimitedListToStringArray(setting, "=", " ");
|
||||
if (columnValuePair[1].startsWith(":") || columnValuePair[1].equals("?")) {
|
||||
extractedColumns.add(columnValuePair[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.cassandra;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.cloud.fn.consumer.cassandra.domain.Book;
|
||||
import org.cassandraunit.spring.CassandraUnitDependencyInjectionIntegrationTestExecutionListener;
|
||||
import org.cassandraunit.spring.EmbeddedCassandra;
|
||||
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.core.WriteResult;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
|
||||
listeners = CassandraUnitDependencyInjectionIntegrationTestExecutionListener.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
|
||||
properties = {
|
||||
"spring.data.cassandra.keyspaceName=" + CassandraConsumerApplicationTests.CASSANDRA_KEYSPACE,
|
||||
"spring.data.cassandra.localDatacenter=datacenter1",
|
||||
"cassandra.cluster.createKeyspace=true" })
|
||||
@EmbeddedCassandra(configuration = EmbeddedCassandraServerHelper.CASSANDRA_RNDPORT_YML_FILE, timeout = 120000)
|
||||
@DirtiesContext
|
||||
abstract class CassandraConsumerApplicationTests {
|
||||
|
||||
static final String CASSANDRA_KEYSPACE = "test";
|
||||
|
||||
@Autowired
|
||||
protected CassandraOperations cassandraTemplate;
|
||||
|
||||
@Autowired
|
||||
protected Function<Object, Mono<? extends WriteResult>> cassandraConsumer;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
EmbeddedCassandraServerHelper.getSession();
|
||||
System.setProperty("spring.data.cassandra.contactPoints",
|
||||
EmbeddedCassandraServerHelper.getHost() + ':' + EmbeddedCassandraServerHelper.getNativeTransportPort());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void cleanup() {
|
||||
System.clearProperty("spring.data.cassandra.contactPoints");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
this.cassandraTemplate.truncate(Book.class);
|
||||
}
|
||||
|
||||
protected static List<Book> getBookList(int numBooks) {
|
||||
|
||||
List<Book> books = new ArrayList<>();
|
||||
|
||||
Book b;
|
||||
for (int i = 0; i < numBooks; i++) {
|
||||
b = new Book();
|
||||
b.setIsbn(UUID.randomUUID());
|
||||
b.setTitle("Spring Cloud Data Flow Guide");
|
||||
b.setAuthor("SCDF Guru");
|
||||
b.setPages(i * 10 + 5);
|
||||
b.setInStock(true);
|
||||
b.setSaleDate(LocalDate.now());
|
||||
books.add(b);
|
||||
}
|
||||
|
||||
return books;
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.cassandra;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
|
||||
import org.springframework.cloud.fn.consumer.cassandra.domain.Book;
|
||||
import org.springframework.data.cassandra.core.WriteResult;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@DisabledOnOs(OS.WINDOWS)
|
||||
@TestPropertySource(properties = {
|
||||
"spring.data.cassandra.schema-action=RECREATE",
|
||||
"cassandra.cluster.entity-base-packages=io.pivotal.java.function.cassandra.consumer.domain" })
|
||||
class CassandraEntityInsertTests extends CassandraConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void testInsert() {
|
||||
Book book = new Book();
|
||||
book.setIsbn(UUID.randomUUID());
|
||||
book.setTitle("Spring Integration Cassandra");
|
||||
book.setAuthor("Cassandra Guru");
|
||||
book.setPages(521);
|
||||
book.setSaleDate(LocalDate.now());
|
||||
book.setInStock(true);
|
||||
|
||||
Mono<? extends WriteResult> result = this.cassandraConsumer.apply(book);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNextCount(1)
|
||||
.then(() ->
|
||||
assertThat(this.cassandraTemplate.query(Book.class)
|
||||
.count())
|
||||
.isEqualTo(1))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.cassandra;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.fn.consumer.cassandra.domain.Book;
|
||||
import org.springframework.data.cassandra.core.WriteResult;
|
||||
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@DisabledOnOs(OS.WINDOWS)
|
||||
@TestPropertySource(properties = {
|
||||
"cassandra.cluster.init-script=init-db.cql",
|
||||
"cassandra.ingest-query=" +
|
||||
"insert into book (isbn, title, author, pages, saleDate, inStock) values (?, ?, ?, ?, ?, ?)" })
|
||||
class CassandraIngestInsertTests extends CassandraConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
void testIngestQuery(@Autowired ObjectMapper objectMapper) throws Exception {
|
||||
List<Book> books = getBookList(5);
|
||||
|
||||
Jackson2JsonObjectMapper mapper = new Jackson2JsonObjectMapper(objectMapper);
|
||||
|
||||
Mono<? extends WriteResult> result =
|
||||
this.cassandraConsumer.apply(mapper.toJson(books));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNextCount(1)
|
||||
.then(() ->
|
||||
assertThat(this.cassandraTemplate.query(Book.class)
|
||||
.count())
|
||||
.isEqualTo(5))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.cassandra;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.fn.consumer.cassandra.domain.Book;
|
||||
import org.springframework.data.cassandra.core.WriteResult;
|
||||
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@DisabledOnOs(OS.WINDOWS)
|
||||
@TestPropertySource(properties = {
|
||||
"cassandra.cluster.init-script=init-db.cql",
|
||||
"cassandra.ingest-query=" +
|
||||
"insert into book (isbn, title, author, pages, saleDate, inStock) " +
|
||||
"values (:myIsbn, :myTitle, :myAuthor, ?, ?, ?)" })
|
||||
class CassandraIngestNamedParamsTests extends CassandraConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
void testIngestQuery(@Autowired ObjectMapper objectMapper) throws Exception {
|
||||
List<Book> books = getBookList(5);
|
||||
|
||||
Jackson2JsonObjectMapper mapper = new Jackson2JsonObjectMapper(objectMapper);
|
||||
|
||||
String booksJsonWithNamedParams = mapper.toJson(books);
|
||||
booksJsonWithNamedParams = StringUtils.replace(booksJsonWithNamedParams, "isbn", "myIsbn");
|
||||
booksJsonWithNamedParams = StringUtils.replace(booksJsonWithNamedParams, "title", "myTitle");
|
||||
booksJsonWithNamedParams = StringUtils.replace(booksJsonWithNamedParams, "author", "myAuthor");
|
||||
|
||||
Mono<? extends WriteResult> result =
|
||||
this.cassandraConsumer.apply(booksJsonWithNamedParams);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNextCount(1)
|
||||
.then(() ->
|
||||
assertThat(this.cassandraTemplate.query(Book.class)
|
||||
.count())
|
||||
.isEqualTo(5))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.cassandra;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.fn.consumer.cassandra.domain.Book;
|
||||
import org.springframework.data.cassandra.core.WriteResult;
|
||||
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@DisabledOnOs(OS.WINDOWS)
|
||||
@TestPropertySource(properties = {
|
||||
"cassandra.cluster.init-script=init-db.cql",
|
||||
"cassandra.ingest-query=" +
|
||||
"update book set inStock = :inStock, author = :author, pages = :pages, " +
|
||||
"saleDate = :saleDate, title = :title where isbn = :isbn",
|
||||
"cassandra.queryType=UPDATE" })
|
||||
class CassandraIngestUpdateTests extends CassandraConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
void testIngestQuery(@Autowired ObjectMapper objectMapper) throws Exception {
|
||||
List<Book> books = getBookList(5);
|
||||
|
||||
Jackson2JsonObjectMapper mapper = new Jackson2JsonObjectMapper(objectMapper);
|
||||
|
||||
Mono<? extends WriteResult> result =
|
||||
this.cassandraConsumer.apply(mapper.toJson(books));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNextCount(1)
|
||||
.then(() ->
|
||||
assertThat(this.cassandraTemplate.query(Book.class)
|
||||
.count())
|
||||
.isEqualTo(5))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.cassandra.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
import org.springframework.data.cassandra.core.mapping.Table;
|
||||
|
||||
/**
|
||||
* Test POJO
|
||||
*
|
||||
* @author David Webb
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Table("book")
|
||||
public class Book {
|
||||
|
||||
@PrimaryKey
|
||||
private UUID isbn;
|
||||
|
||||
private String title;
|
||||
|
||||
private String author;
|
||||
|
||||
private int pages;
|
||||
|
||||
private LocalDate saleDate;
|
||||
|
||||
private boolean inStock;
|
||||
|
||||
public Book() {
|
||||
}
|
||||
|
||||
public Book(UUID isbn, String title, String author) {
|
||||
this.isbn = isbn;
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the isbn.
|
||||
*/
|
||||
public UUID getIsbn() {
|
||||
return this.isbn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the saleDate.
|
||||
*/
|
||||
public LocalDate getSaleDate() {
|
||||
return this.saleDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param saleDate The saleDate to set.
|
||||
*/
|
||||
public void setSaleDate(LocalDate saleDate) {
|
||||
this.saleDate = saleDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the inStock.
|
||||
*/
|
||||
public boolean isInStock() {
|
||||
return this.inStock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inStock The isInStock to set.
|
||||
*/
|
||||
public void setInStock(boolean inStock) {
|
||||
this.inStock = inStock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isbn The isbn to set.
|
||||
*/
|
||||
public void setIsbn(UUID isbn) {
|
||||
this.isbn = isbn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the title.
|
||||
*/
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title The title to set.
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the author.
|
||||
*/
|
||||
public String getAuthor() {
|
||||
return this.author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param author The author to set.
|
||||
*/
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the pages.
|
||||
*/
|
||||
public int getPages() {
|
||||
return this.pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pages The pages to set.
|
||||
*/
|
||||
public void setPages(int pages) {
|
||||
this.pages = pages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ("isbn -> " + this.isbn) + "\n" + "tile -> " + this.title + "\n" + "author -> " + this.author
|
||||
+ "\n" + "pages -> " + this.pages + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
10
consumer/cassandra-consumer/src/test/resources/init-db.cql
Normal file
10
consumer/cassandra-consumer/src/test/resources/init-db.cql
Normal file
@@ -0,0 +1,10 @@
|
||||
DROP TABLE IF EXISTS book;
|
||||
|
||||
CREATE TABLE book (
|
||||
isbn uuid PRIMARY KEY,
|
||||
author text,
|
||||
instock boolean,
|
||||
pages int,
|
||||
saledate date,
|
||||
title text
|
||||
);
|
||||
28
consumer/counter-consumer/.gitignore
vendored
Normal file
28
consumer/counter-consumer/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
54
consumer/counter-consumer/pom.xml
Normal file
54
consumer/counter-consumer/pom.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>counter-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>counter-consumer</name>
|
||||
<description>Spring Native Consumer for computing counters</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>payload-converter-function</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ CounterConsumerProperties.class })
|
||||
public class CounterConsumerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<String, Expression> stringToSpelFunction(@Lazy EvaluationContext evaluationContext) {
|
||||
return new StringToSpelConversionFunction(evaluationContext);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationPropertiesBinding
|
||||
public Converter<String, Expression> propertiesSpelConverter(Function<String, Expression> stringToSpelFunction) {
|
||||
return new Converter<String, Expression>() { // NOTE Using lambda causes Java Generics issues.
|
||||
@Override
|
||||
public Expression convert(String source) {
|
||||
return stringToSpelFunction.apply(source);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean(name = "counterConsumer")
|
||||
public Consumer<Message<?>> counterConsumer(CounterConsumerProperties properties, MeterRegistry[] meterRegistries,
|
||||
@Qualifier(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME) EvaluationContext context) {
|
||||
|
||||
return message -> {
|
||||
|
||||
String counterName = properties.getComputedNameExpression().getValue(context, message, CharSequence.class).toString();
|
||||
|
||||
// All fixed tags together are passed with every counter increment.
|
||||
Tags fixedTags = this.toTags(properties.getTag().getFixed());
|
||||
|
||||
double amount = properties.getComputedAmountExpression().getValue(context, message, double.class);
|
||||
|
||||
Map<String, List<Tag>> allGroupedTags = new HashMap<>();
|
||||
// Tag Expressions Counter
|
||||
if (properties.getTag().getExpression() != null) {
|
||||
|
||||
Map<String, List<Tag>> groupedTags = properties.getTag().getExpression().entrySet().stream()
|
||||
// maps a <name, expr> pair into [<name, expr#val_1>, ... <name, expr#val_N>] Tag array.
|
||||
.map(namedExpression ->
|
||||
toList(namedExpression.getValue().getValue(context, message)).stream()
|
||||
.map(tagValue -> Tag.of(namedExpression.getKey(), tagValue))
|
||||
.collect(Collectors.toList())).flatMap(List::stream)
|
||||
.collect(Collectors.groupingBy(tag -> tag.getKey(), Collectors.toList()));
|
||||
allGroupedTags.putAll(groupedTags);
|
||||
}
|
||||
|
||||
this.count(meterRegistries, counterName, fixedTags, allGroupedTags, amount);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a key/value Map into Tag(key,value) list. Filters out the empty key/value pairs.
|
||||
* @param keyValueMap key/value map to convert into tags.
|
||||
* @return Returns Tags list representing every non-empty key/value pair.
|
||||
*/
|
||||
protected Tags toTags(Map<String, String> keyValueMap) {
|
||||
return CollectionUtils.isEmpty(keyValueMap) ? Tags.empty() :
|
||||
Tags.of(keyValueMap.entrySet().stream()
|
||||
.filter(e -> StringUtils.hasText(e.getKey()) && StringUtils.hasText(e.getValue()))
|
||||
.map(e -> Tag.of(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the input value into an list of values. If the value is not a collection/array type the result
|
||||
* is a single element list. For collection/array input value the result is the list of stringifie content of
|
||||
* this collection.
|
||||
* @param value input value can be array, collection or single value.
|
||||
* @return Returns value list.
|
||||
*/
|
||||
protected List<String> toList(Object value) {
|
||||
if (value == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if ((value instanceof Collection) || ObjectUtils.isArray(value)) {
|
||||
Collection<?> valueCollection = (value instanceof Collection) ? (Collection<?>) value
|
||||
: Arrays.asList(ObjectUtils.toObjectArray(value));
|
||||
|
||||
return valueCollection.stream()
|
||||
.filter(v -> v != null)
|
||||
.map(Object::toString)
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
else {
|
||||
return Arrays.asList(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void count(MeterRegistry[] meterRegistries, String counterName, Tags fixedTags, Map<String, List<Tag>> groupedTags, double amount) {
|
||||
if (!CollectionUtils.isEmpty(groupedTags)) {
|
||||
groupedTags.values().stream().map(List::size).max(Integer::compareTo).ifPresent(
|
||||
max -> {
|
||||
for (int i = 0; i < max; i++) {
|
||||
Tags currentTags = Tags.of(fixedTags);
|
||||
for (Map.Entry<String, List<Tag>> e : groupedTags.entrySet()) {
|
||||
currentTags = (e.getValue().size() > i) ?
|
||||
currentTags.and(e.getValue().get(i)) :
|
||||
currentTags.and(Tags.of(e.getKey(), ""));
|
||||
}
|
||||
|
||||
// Increment the counterName increment for every configured MaterRegistry.
|
||||
for (MeterRegistry meterRegistry : meterRegistries) {
|
||||
meterRegistry.counter(counterName, currentTags).increment(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Increment the counterName increment for every configured MaterRegistry.
|
||||
for (MeterRegistry meterRegistry : meterRegistries) {
|
||||
meterRegistry.counter(counterName, fixedTags).increment(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SimpleMeterRegistry simpleMeterRegistry() {
|
||||
return new SimpleMeterRegistry();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ConfigurationProperties("counter")
|
||||
@Validated
|
||||
public class CounterConsumerProperties {
|
||||
|
||||
/**
|
||||
* The default name of the increment
|
||||
*/
|
||||
@Value("${spring.application.name:counts}")
|
||||
private String defaultName;
|
||||
|
||||
/**
|
||||
* The name of the counter to increment. The 'name' and 'nameExpression' are mutually exclusive.
|
||||
* Only one can be set.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* A SpEL expression (against the incoming Message) to derive the name of the counter to increment.
|
||||
* The 'name' and 'nameExpression' are mutually exclusive. Only one can be set.
|
||||
*/
|
||||
private Expression nameExpression;
|
||||
|
||||
/**
|
||||
* A SpEL expression (against the incoming Message) to derive the amount to add to the counter.
|
||||
* If not set the counter is incremented by 1.0
|
||||
*/
|
||||
private Expression amountExpression;
|
||||
|
||||
/**
|
||||
* Enables counting the number of messages processed. Uses the 'message.' counter name prefix to distinct it
|
||||
* form the expression based counter. The message counter includes the fixed tags when provided.
|
||||
*/
|
||||
private boolean messageCounterEnabled = true;
|
||||
|
||||
/**
|
||||
* Fixed and computed tags to be assignee with the counter increment measurement.
|
||||
*/
|
||||
private MetricsTag tag = new MetricsTag();
|
||||
|
||||
public static class MetricsTag {
|
||||
|
||||
/**
|
||||
* Custom tags assigned to every counter increment measurements.
|
||||
* This is a map so the property convention fixed tags is: counter.tag.fixed.[tag-name]=[tag-value]
|
||||
*/
|
||||
private Map<String, String> fixed;
|
||||
|
||||
/**
|
||||
* Computes tags from SpEL expression.
|
||||
* Single SpEL expression can produce an array of values, which in turn means distinct name/value tags.
|
||||
* Every name/value tag will produce a separate counter increment.
|
||||
* Tag expression format is: counter.tag.expression.[tag-name]=[SpEL expression]
|
||||
*/
|
||||
private Map<String, Expression> expression;
|
||||
|
||||
public Map<String, String> getFixed() {
|
||||
return fixed;
|
||||
}
|
||||
|
||||
public void setFixed(Map<String, String> fixed) {
|
||||
this.fixed = fixed;
|
||||
}
|
||||
|
||||
public Map<String, Expression> getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
public void setExpression(Map<String, Expression> expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetricsTag{" +
|
||||
"fixed=" + fixed +
|
||||
", expression=" + expression +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public MetricsTag getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (name == null && nameExpression == null) {
|
||||
return defaultName;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Expression getNameExpression() {
|
||||
return nameExpression;
|
||||
}
|
||||
|
||||
public void setNameExpression(Expression nameExpression) {
|
||||
this.nameExpression = nameExpression;
|
||||
}
|
||||
|
||||
public Expression getAmountExpression() {
|
||||
return amountExpression;
|
||||
}
|
||||
|
||||
public void setAmountExpression(Expression amountExpression) {
|
||||
this.amountExpression = amountExpression;
|
||||
}
|
||||
|
||||
public Expression getComputedAmountExpression() {
|
||||
return (amountExpression != null ? amountExpression : new LiteralExpression("1.0"));
|
||||
}
|
||||
|
||||
public Expression getComputedNameExpression() {
|
||||
return (nameExpression != null ? nameExpression : new LiteralExpression(getName()));
|
||||
}
|
||||
|
||||
public boolean isMessageCounterEnabled() {
|
||||
return messageCounterEnabled;
|
||||
}
|
||||
|
||||
public void setMessageCounterEnabled(boolean messageCounterEnabled) {
|
||||
this.messageCounterEnabled = messageCounterEnabled;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "exactly one of 'name' and 'nameExpression' must be set")
|
||||
public boolean isExclusiveOptions() {
|
||||
return getName() != null ^ getNameExpression() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CounterFunctionProperties{" +
|
||||
"defaultName='" + defaultName + '\'' +
|
||||
", name=" + name +
|
||||
", tag=" + tag +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* Converter from String to Spring Expression.
|
||||
*
|
||||
* TODO: This could be a top level project.
|
||||
*/
|
||||
public class StringToSpelConversionFunction implements Function<String, Expression> {
|
||||
|
||||
private final SpelExpressionParser parser;
|
||||
|
||||
private final EvaluationContext evaluationContext;
|
||||
|
||||
public StringToSpelConversionFunction(EvaluationContext evaluationContext) {
|
||||
this(new SpelExpressionParser(), evaluationContext);
|
||||
}
|
||||
|
||||
public StringToSpelConversionFunction(SpelExpressionParser parser, EvaluationContext evaluationContext) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression apply(String source) {
|
||||
try {
|
||||
Expression expression = parser.parseExpression(source);
|
||||
if (expression instanceof SpelExpression) {
|
||||
((SpelExpression) expression).setEvaluationContext(evaluationContext);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
catch (ParseException e) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Could not convert '%s' into a SpEL expression", source), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class ConverterFunctionAdapter<S, T> implements Converter<S, T> {
|
||||
|
||||
private Function<S, T> function;
|
||||
|
||||
public ConverterFunctionAdapter(Function<S, T> function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(S s) {
|
||||
return this.function.apply(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"counter.name=counter666",
|
||||
"counter.tag.expression.foo='bar'",
|
||||
"counter.amount-expression=payload.length()"
|
||||
})
|
||||
class CountWithAmountTest extends CounterConsumerParentTest {
|
||||
|
||||
@Test
|
||||
void testCounterSink() {
|
||||
String message = "hello world message";
|
||||
double messageSize = Long.valueOf(message.length()).doubleValue();
|
||||
counterConsumer.accept(new GenericMessage(message));
|
||||
assertThat(meterRegistry.find("counter666").counter().count()).isEqualTo(messageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
@DirtiesContext
|
||||
public class CounterConsumerParentTest {
|
||||
|
||||
@Autowired
|
||||
protected SimpleMeterRegistry meterRegistry;
|
||||
|
||||
@Autowired
|
||||
protected Consumer<Message<?>> counterConsumer;
|
||||
|
||||
protected Message<byte[]> message(String payload) {
|
||||
return MessageBuilder.withPayload(payload.getBytes()).build();
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"counter.name=counter666",
|
||||
"counter.tag.fixed.foo=",
|
||||
"counter.tag.expression.tag666=#jsonPath(payload,'$..noField')",
|
||||
"counter.tag.expression.test=#jsonPath(payload,'$..test')",
|
||||
})
|
||||
class EmptyTagsTests extends CounterConsumerParentTest {
|
||||
|
||||
@Test
|
||||
void testCounterSink() {
|
||||
|
||||
counterConsumer.accept(message("{\"test\": \"Bar\"}"));
|
||||
|
||||
Collection<Counter> fixedTagsCounters = meterRegistry.find("counter666").tagKeys("foo").counters();
|
||||
assertThat(fixedTagsCounters.size()).isEqualTo(0);
|
||||
|
||||
Collection<Counter> expressionTagsCounters = meterRegistry.find("counter666").tagKeys("tag666").counters();
|
||||
assertThat(expressionTagsCounters.size()).isEqualTo(0);
|
||||
|
||||
Collection<Counter> testExpTagsCounters = meterRegistry.find("counter666").tagKeys("test").counters();
|
||||
assertThat(testExpTagsCounters.size()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"counter.name-expression=payload"
|
||||
})
|
||||
public class ExpressionCounterNameTests extends CounterConsumerParentTest {
|
||||
|
||||
@Test
|
||||
void testCounterSink() {
|
||||
IntStream.range(0, 13).forEach(i -> counterConsumer.accept(new GenericMessage("hello")));
|
||||
assertThat(meterRegistry.find("hello").counter().count()).isEqualTo(13.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"counter.name=counter666",
|
||||
"counter.tag.fixed.foo=bar",
|
||||
"counter.tag.fixed.gork=bork"
|
||||
})
|
||||
public class FixedTagsTests extends CounterConsumerParentTest {
|
||||
|
||||
@Test
|
||||
void testCounterSink() {
|
||||
IntStream.range(0, 13).forEach(i -> counterConsumer.accept(new GenericMessage("hello")));
|
||||
Meter counterMeter = meterRegistry.find("counter666").meter();
|
||||
assertThat(StreamSupport.stream(counterMeter.measure().spliterator(), false)
|
||||
.mapToDouble(m -> m.getValue()).sum()).isEqualTo(13.0);
|
||||
|
||||
assertThat(counterMeter.getId().getTags().size()).isEqualTo(2);
|
||||
assertThat(counterMeter.getId().getTag("foo")).isEqualTo("bar");
|
||||
assertThat(counterMeter.getId().getTag("gork")).isEqualTo("bork");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"counter.name=counter666",
|
||||
"counter.tag.expression.foo='bar'",
|
||||
"counter.tag.expression.gork='bork'"
|
||||
})
|
||||
public class LiteralTagExpressionsTests extends CounterConsumerParentTest {
|
||||
|
||||
@Test
|
||||
void testCounterSink() {
|
||||
|
||||
IntStream.range(0, 13).forEach(i -> counterConsumer.accept(new GenericMessage("hello")));
|
||||
|
||||
Counter fooCounter = meterRegistry.find("counter666").tag("foo", "bar").counter();
|
||||
assertThat(fooCounter.count()).isEqualTo(13.0);
|
||||
|
||||
Counter gorkCounter = meterRegistry.find("counter666").tag("gork", "bork").counter();
|
||||
assertThat(gorkCounter.count()).isEqualTo(13.0);
|
||||
|
||||
assertThat(fooCounter.getId()).isEqualTo(gorkCounter.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.counter;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"counter.name=counter666",
|
||||
"counter.tag.fixed.foo=",
|
||||
"counter.tag.expression.tag666=#jsonPath(payload,'$..noField')",
|
||||
"counter.tag.expression.test=#jsonPath(payload,'$..test')",
|
||||
})
|
||||
public class NullTagsTests extends CounterConsumerParentTest {
|
||||
|
||||
@Test
|
||||
void testCounterSink() {
|
||||
|
||||
counterConsumer.accept(message("{\"test\": null}"));
|
||||
|
||||
Collection<Counter> fixedTagsCounters = meterRegistry.find("counter666").tagKeys("foo").counters();
|
||||
assertThat(fixedTagsCounters.size()).isEqualTo(0);
|
||||
|
||||
Collection<Counter> expressionTagsCounters = meterRegistry.find("counter666").tagKeys("tag666").counters();
|
||||
assertThat(expressionTagsCounters.size()).isEqualTo(0);
|
||||
|
||||
Collection<Counter> testExpTagsCounters = meterRegistry.find("counter666").tagKeys("test").counters();
|
||||
assertThat(testExpTagsCounters.size()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
41
consumer/file-consumer/pom.xml
Normal file
41
consumer/file-consumer/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>file-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>file-consumer</name>
|
||||
<description>file consumer</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-file</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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.cloud.fn.consumer.file;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.FileWritingMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(FileConsumerProperties.class)
|
||||
public class FileConsumerConfiguration {
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
private final FileConsumerProperties properties;
|
||||
|
||||
public FileConsumerConfiguration(FileConsumerProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Consumer<Message<?>> fileConsumer() {
|
||||
return fileWritingMessageHandler()::handleMessage;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FileWritingMessageHandler fileWritingMessageHandler() {
|
||||
FileWritingMessageHandler handler = (properties.getDirectoryExpression() != null)
|
||||
? new FileWritingMessageHandler(EXPRESSION_PARSER.parseExpression(properties.getDirectoryExpression()))
|
||||
: new FileWritingMessageHandler(properties.getDirectory());
|
||||
handler.setAutoCreateDirectory(true);
|
||||
handler.setAppendNewLine(!properties.isBinary());
|
||||
handler.setCharset(properties.getCharset());
|
||||
handler.setExpectReply(false);
|
||||
handler.setFileExistsMode(properties.getMode());
|
||||
handler.setFileNameGenerator(fileNameGenerator());
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FileNameGenerator fileNameGenerator() {
|
||||
DefaultFileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
|
||||
fileNameGenerator.setExpression(properties.getNameExpression());
|
||||
return fileNameGenerator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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.cloud.fn.consumer.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Properties for the file sink.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@ConfigurationProperties("file.consumer")
|
||||
@Validated
|
||||
public class FileConsumerProperties {
|
||||
|
||||
static final String DEFAULT_DIR = System.getProperty("java.io.tmpdir") + "file-consumer";
|
||||
|
||||
private static final String DEFAULT_NAME = "file-consumer";
|
||||
|
||||
/**
|
||||
* A flag to indicate whether adding a newline after the write should be suppressed.
|
||||
*/
|
||||
private boolean binary = false;
|
||||
|
||||
/**
|
||||
* The charset to use when writing text content.
|
||||
*/
|
||||
private String charset = "UTF-8";
|
||||
|
||||
/**
|
||||
* The parent directory of the target file.
|
||||
*/
|
||||
private File directory = new File(DEFAULT_DIR);
|
||||
|
||||
/**
|
||||
* The expression to evaluate for the parent directory of the target file.
|
||||
*/
|
||||
private String directoryExpression;
|
||||
|
||||
/**
|
||||
* The FileExistsMode to use if the target file already exists.
|
||||
*/
|
||||
private FileExistsMode mode = FileExistsMode.APPEND;
|
||||
|
||||
/**
|
||||
* The name of the target file.
|
||||
*/
|
||||
private String name = DEFAULT_NAME;
|
||||
|
||||
/**
|
||||
* The expression to evaluate for the name of the target file.
|
||||
*/
|
||||
private String nameExpression;
|
||||
|
||||
/**
|
||||
* The suffix to append to file name.
|
||||
*/
|
||||
private String suffix = "";
|
||||
|
||||
public boolean isBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
public void setBinary(boolean binary) {
|
||||
this.binary = binary;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public String getDirectoryExpression() {
|
||||
return directoryExpression;
|
||||
}
|
||||
|
||||
public void setDirectoryExpression(String directoryExpression) {
|
||||
this.directoryExpression = directoryExpression;
|
||||
}
|
||||
|
||||
public FileExistsMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(FileExistsMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getNameExpression() {
|
||||
return (nameExpression != null)
|
||||
? nameExpression + " + '" + getSuffix() + "'"
|
||||
: "'" + name + getSuffix() + "'";
|
||||
}
|
||||
|
||||
public void setNameExpression(String nameExpression) {
|
||||
this.nameExpression = nameExpression;
|
||||
}
|
||||
|
||||
public String getSuffix() {
|
||||
String suffixWithDotIfNecessary = "";
|
||||
if (StringUtils.hasText(suffix)) {
|
||||
suffixWithDotIfNecessary = suffix.startsWith(".") ? suffix : "." + suffix;
|
||||
}
|
||||
return suffixWithDotIfNecessary;
|
||||
}
|
||||
|
||||
public void setSuffix(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "Exactly one of 'name' or 'nameExpression' must be set")
|
||||
public boolean isMutuallyExclusiveNameAndNameExpression() {
|
||||
return DEFAULT_NAME.equals(name) || nameExpression == null;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "Exactly one of 'directory' or 'directoryExpression' must be set")
|
||||
public boolean isMutuallyExclusiveDirectoryAndDirectoryExpression() {
|
||||
return new File(DEFAULT_DIR).equals(directory) || directoryExpression == null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.file;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
/**
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@SpringBootTest
|
||||
@DirtiesContext
|
||||
public class AbstractFileConsumerTests {
|
||||
|
||||
@TempDir
|
||||
static Path tempDir;
|
||||
|
||||
@Autowired
|
||||
Consumer<Message<?>> fileConsumer;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() {
|
||||
System.setProperty("file.consumer.directory", tempDir.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterAll() {
|
||||
System.clearProperty("file.consumer.directory");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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.cloud.fn.consumer.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@TestPropertySource(properties = "file.consumer.binary = true")
|
||||
public class BinaryFileTests extends AbstractFileConsumerTests {
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
fileConsumer.accept(MessageBuilder.withPayload("hello file-consumer".getBytes()).build());
|
||||
File file = new File(tempDir.toFile(), "file-consumer");
|
||||
assertThat(file.exists()).isTrue();
|
||||
byte[] results = FileCopyUtils.copyToByteArray(file);
|
||||
assertThat("hello file-consumer".getBytes()).isEqualTo(results);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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.cloud.fn.consumer.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*
|
||||
* We don't need a separate SpringBootApplication for this test as there is already one available in this package.
|
||||
* {@link AbstractFileConsumerTests}.
|
||||
*/
|
||||
@SpringBootTest(properties = {"file.consumer.nameExpression = payload.substring(0, 4)",
|
||||
"file.consumer.directoryExpression = '${java.io.tmpdir}'+'/'+headers.dir",
|
||||
"file.consumer.suffix=out"})
|
||||
@DirtiesContext
|
||||
public class ExpressionTests {
|
||||
|
||||
@TempDir
|
||||
static Path tempDir;
|
||||
|
||||
@Autowired
|
||||
Consumer<Message<?>> fileConsumer;
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
fileConsumer.accept(MessageBuilder.withPayload("this is something").setHeader("dir", "expression").build());
|
||||
File file = new File(System.getProperty("java.io.tmpdir") + File.separator + "expression", "this.out");
|
||||
file.deleteOnExit();
|
||||
assertThat(file.exists()).isTrue();
|
||||
assertThat("this is something" + System.lineSeparator())
|
||||
.isEqualTo(FileCopyUtils.copyToString(new FileReader(file)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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.cloud.fn.consumer.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@TestPropertySource(properties = {"file.consumer.name = test", "file.consumer.suffix=txt"})
|
||||
public class TextFileTests extends AbstractFileConsumerTests {
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
fileConsumer.accept(MessageBuilder.withPayload("hello file-consumer").build());
|
||||
File file = new File(tempDir.toFile(), "test.txt");
|
||||
assertThat(file.exists()).isTrue();
|
||||
assertThat("hello file-consumer" + System.lineSeparator())
|
||||
.isEqualTo(FileCopyUtils.copyToString(new FileReader(file)));
|
||||
}
|
||||
|
||||
}
|
||||
63
consumer/jdbc-consumer/pom.xml
Normal file
63
consumer/jdbc-consumer/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jdbc-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>jdbc-consumer</name>
|
||||
<description>jdbc consumer</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
|
||||
/**
|
||||
* An in-memory script crafted for dropping-creating the table we're working with.
|
||||
* All columns are created as VARCHAR(2000).
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
public class DefaultInitializationScriptResource extends ByteArrayResource {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultInitializationScriptResource.class);
|
||||
|
||||
public DefaultInitializationScriptResource(String tableName, Collection<String> columns) {
|
||||
super(scriptFor(tableName, columns).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String scriptFor(String tableName, Collection<String> columns) {
|
||||
StringBuilder result = new StringBuilder("DROP TABLE ");
|
||||
result.append(tableName).append(";\n\n");
|
||||
|
||||
result.append("CREATE TABLE ").append(tableName).append('(');
|
||||
int i = 0;
|
||||
for (String column : columns) {
|
||||
if (i++ > 0) {
|
||||
result.append(", ");
|
||||
}
|
||||
result.append(column).append(" VARCHAR(2000)");
|
||||
}
|
||||
result.append(");\n");
|
||||
logger.debug(String.format("Generated the following initializing script for table %s:\n%s", tableName,
|
||||
result.toString()));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.SpelParseException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.aggregator.DefaultAggregatingMessageGroupProcessor;
|
||||
import org.springframework.integration.aggregator.MessageCountReleaseStrategy;
|
||||
import org.springframework.integration.config.AggregatorFactoryBean;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlowBuilder;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.integration.jdbc.JdbcMessageHandler;
|
||||
import org.springframework.integration.jdbc.SqlParameterSourceFactory;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor;
|
||||
import org.springframework.integration.store.MessageGroupStore;
|
||||
import org.springframework.integration.store.SimpleMessageStore;
|
||||
import org.springframework.integration.support.MutableMessage;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(JdbcConsumerProperties.class)
|
||||
public class JdbcConsumerConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JdbcConsumerConfiguration.class);
|
||||
|
||||
private static final Object NOT_SET = new Object();
|
||||
|
||||
private final JdbcConsumerProperties properties;
|
||||
|
||||
private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
|
||||
private EvaluationContext evaluationContext;
|
||||
|
||||
public JdbcConsumerConfiguration(JdbcConsumerProperties properties, BeanFactory beanFactory) {
|
||||
this.properties = properties;
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(beanFactory);
|
||||
StandardEvaluationContext standardEvaluationContext = (StandardEvaluationContext) this.evaluationContext;
|
||||
standardEvaluationContext.addPropertyAccessor(new JsonPropertyAccessor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
IntegrationFlow jdbcConsumerFlow(@Qualifier("aggregator") MessageHandler aggregator,
|
||||
JdbcMessageHandler jdbcMessageHandler) {
|
||||
|
||||
final IntegrationFlowBuilder builder =
|
||||
IntegrationFlows.from(Consumer.class, gateway -> gateway.beanName("jdbcConsumer"));
|
||||
if (properties.getBatchSize() > 1 || properties.getIdleTimeout() > 0) {
|
||||
builder.handle(aggregator);
|
||||
}
|
||||
return builder.handle(jdbcMessageHandler).get();
|
||||
}
|
||||
|
||||
@Bean
|
||||
FactoryBean<MessageHandler> aggregator(MessageGroupStore messageGroupStore) {
|
||||
AggregatorFactoryBean aggregatorFactoryBean = new AggregatorFactoryBean();
|
||||
aggregatorFactoryBean.setCorrelationStrategy(message -> message.getPayload().getClass().getName());
|
||||
aggregatorFactoryBean.setReleaseStrategy(new MessageCountReleaseStrategy(this.properties.getBatchSize()));
|
||||
if (this.properties.getIdleTimeout() >= 0) {
|
||||
aggregatorFactoryBean.setGroupTimeoutExpression(new ValueExpression<>(this.properties.getIdleTimeout()));
|
||||
}
|
||||
aggregatorFactoryBean.setMessageStore(messageGroupStore);
|
||||
aggregatorFactoryBean.setProcessorBean(new DefaultAggregatingMessageGroupProcessor());
|
||||
aggregatorFactoryBean.setExpireGroupsUponCompletion(true);
|
||||
aggregatorFactoryBean.setSendPartialResultOnExpiry(true);
|
||||
return aggregatorFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageGroupStore messageGroupStore() {
|
||||
SimpleMessageStore messageGroupStore = new SimpleMessageStore();
|
||||
messageGroupStore.setTimeoutOnIdle(true);
|
||||
messageGroupStore.setCopyOnGet(false);
|
||||
return messageGroupStore;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcMessageHandler jdbcMessageHandler(DataSource dataSource) {
|
||||
final MultiValueMap<String, Expression> columnExpressionVariations = new LinkedMultiValueMap<>();
|
||||
for (Map.Entry<String, String> entry : this.properties.getColumnsMap().entrySet()) {
|
||||
String value = entry.getValue();
|
||||
columnExpressionVariations.add(entry.getKey(), this.spelExpressionParser.parseExpression(value));
|
||||
if (!value.startsWith("payload")) {
|
||||
String qualified = "payload." + value;
|
||||
try {
|
||||
columnExpressionVariations.add(entry.getKey(),
|
||||
this.spelExpressionParser.parseExpression(qualified));
|
||||
}
|
||||
catch (SpelParseException e) {
|
||||
logger.info("failed to parse qualified fallback expression " + qualified +
|
||||
"; be sure your expression uses the 'payload.' prefix where necessary");
|
||||
}
|
||||
}
|
||||
}
|
||||
JdbcMessageHandler jdbcMessageHandler = new JdbcMessageHandler(dataSource,
|
||||
generateSql(this.properties.getTableName(), columnExpressionVariations.keySet())) {
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(final Message<?> message) {
|
||||
Message<?> convertedMessage = message;
|
||||
if (message.getPayload() instanceof byte[] || message.getPayload() instanceof Iterable) {
|
||||
|
||||
final String contentType = message.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)
|
||||
? message.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()
|
||||
: MimeTypeUtils.APPLICATION_JSON_VALUE;
|
||||
if (message.getPayload() instanceof Iterable) {
|
||||
Stream<Object> messageStream =
|
||||
StreamSupport.stream(((Iterable<?>) message.getPayload()).spliterator(), false)
|
||||
.map(payload -> {
|
||||
if (payload instanceof byte[]) {
|
||||
return convertibleContentType(contentType) ?
|
||||
new String(((byte[]) payload)) : payload;
|
||||
}
|
||||
else {
|
||||
return payload;
|
||||
}
|
||||
});
|
||||
convertedMessage = new MutableMessage<>(messageStream.collect(Collectors.toList()),
|
||||
message.getHeaders());
|
||||
}
|
||||
else {
|
||||
if (convertibleContentType(contentType)) {
|
||||
convertedMessage = new MutableMessage<>(new String(((byte[]) message.getPayload())),
|
||||
message.getHeaders());
|
||||
}
|
||||
}
|
||||
}
|
||||
super.handleMessageInternal(convertedMessage);
|
||||
}
|
||||
};
|
||||
SqlParameterSourceFactory parameterSourceFactory =
|
||||
new ParameterFactory(columnExpressionVariations, this.evaluationContext);
|
||||
jdbcMessageHandler.setSqlParameterSourceFactory(parameterSourceFactory);
|
||||
return jdbcMessageHandler;
|
||||
}
|
||||
|
||||
@ConditionalOnProperty("jdbc.consumer.initialize")
|
||||
@Bean
|
||||
public DataSourceInitializer nonBootDataSourceInitializer(DataSource dataSource, ResourceLoader resourceLoader) {
|
||||
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
|
||||
dataSourceInitializer.setDataSource(dataSource);
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
databasePopulator.setIgnoreFailedDrops(true);
|
||||
dataSourceInitializer.setDatabasePopulator(databasePopulator);
|
||||
if ("true".equals(properties.getInitialize())) {
|
||||
databasePopulator.addScript(
|
||||
new DefaultInitializationScriptResource(this.properties.getTableName(),
|
||||
this.properties.getColumnsMap().keySet()));
|
||||
}
|
||||
else {
|
||||
databasePopulator.addScript(resourceLoader.getResource(this.properties.getInitialize()));
|
||||
}
|
||||
return dataSourceInitializer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static ShorthandMapConverter shorthandMapConverter() {
|
||||
return new ShorthandMapConverter();
|
||||
}
|
||||
|
||||
private static boolean convertibleContentType(String contentType) {
|
||||
return contentType.contains("text") || contentType.contains("json") || contentType.contains("x-spring-tuple");
|
||||
}
|
||||
|
||||
private static String generateSql(String tableName, Set<String> columns) {
|
||||
StringBuilder builder = new StringBuilder("INSERT INTO ");
|
||||
StringBuilder questionMarks = new StringBuilder(") VALUES (");
|
||||
builder.append(tableName).append("(");
|
||||
int i = 0;
|
||||
|
||||
for (String column : columns) {
|
||||
if (i++ > 0) {
|
||||
builder.append(", ");
|
||||
questionMarks.append(", ");
|
||||
}
|
||||
builder.append(column);
|
||||
questionMarks.append(':').append(column);
|
||||
}
|
||||
builder.append(questionMarks).append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static final class ParameterFactory implements SqlParameterSourceFactory {
|
||||
|
||||
private final MultiValueMap<String, Expression> columnExpressions;
|
||||
|
||||
private final EvaluationContext context;
|
||||
|
||||
ParameterFactory(MultiValueMap<String, Expression> columnExpressions, EvaluationContext context) {
|
||||
this.columnExpressions = columnExpressions;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlParameterSource createParameterSource(Object o) {
|
||||
if (!(o instanceof Message)) {
|
||||
throw new IllegalArgumentException("Unable to handle type " + o.getClass().getName());
|
||||
}
|
||||
Message<?> message = (Message<?>) o;
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
for (Map.Entry<String, List<Expression>> entry : this.columnExpressions.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
List<Expression> spels = entry.getValue();
|
||||
Object value = NOT_SET;
|
||||
EvaluationException lastException = null;
|
||||
for (Expression spel : spels) {
|
||||
try {
|
||||
value = spel.getValue(context, message);
|
||||
break;
|
||||
}
|
||||
catch (EvaluationException e) {
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
if (value == NOT_SET) {
|
||||
if (lastException != null) {
|
||||
logger.info("Could not find value for column '" + key + "': " + lastException.getMessage());
|
||||
}
|
||||
parameterSource.addValue(key, null);
|
||||
}
|
||||
else {
|
||||
if (value instanceof JsonPropertyAccessor.ToStringFriendlyJsonNode) {
|
||||
// Need to do some reflection until we have a getter for the Node
|
||||
DirectFieldAccessor dfa = new DirectFieldAccessor(value);
|
||||
JsonNode node = (JsonNode) dfa.getPropertyValue("node");
|
||||
Object valueToUse;
|
||||
if (node == null || node.isNull()) {
|
||||
valueToUse = null;
|
||||
}
|
||||
else if (node.isNumber()) {
|
||||
valueToUse = node.numberValue();
|
||||
}
|
||||
else if (node.isBoolean()) {
|
||||
valueToUse = node.booleanValue();
|
||||
}
|
||||
else {
|
||||
valueToUse = node.textValue();
|
||||
}
|
||||
parameterSource.addValue(key, valueToUse);
|
||||
}
|
||||
else {
|
||||
parameterSource.addValue(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameterSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Artem Bilan
|
||||
* @author Oliver Flasch
|
||||
*/
|
||||
@ConfigurationProperties("jdbc.consumer")
|
||||
public class JdbcConsumerProperties {
|
||||
|
||||
@Autowired
|
||||
private ShorthandMapConverter shorthandMapConverter;
|
||||
|
||||
/**
|
||||
* The name of the table to write into.
|
||||
*/
|
||||
private String tableName = "messages";
|
||||
|
||||
/**
|
||||
* The comma separated colon-based pairs of column names and SpEL expressions for values to insert/update.
|
||||
* Names are used at initialization time to issue the DDL.
|
||||
*/
|
||||
private String columns = "payload:payload.toString()";
|
||||
|
||||
/**
|
||||
* 'true', 'false' or the location of a custom initialization script for the table.
|
||||
*/
|
||||
private String initialize = "false";
|
||||
|
||||
/**
|
||||
* Threshold in number of messages when data will be flushed to database table.
|
||||
*/
|
||||
private int batchSize = 1;
|
||||
|
||||
/**
|
||||
* Idle timeout in milliseconds when data is automatically flushed to database table.
|
||||
*/
|
||||
private long idleTimeout = -1L;
|
||||
|
||||
private Map<String, String> columnsMap;
|
||||
|
||||
public String getTableName() {
|
||||
return this.tableName;
|
||||
}
|
||||
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public String getColumns() {
|
||||
return this.columns;
|
||||
}
|
||||
|
||||
public void setColumns(String columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
public String getInitialize() {
|
||||
return this.initialize;
|
||||
}
|
||||
|
||||
public void setInitialize(String initialize) {
|
||||
this.initialize = initialize;
|
||||
}
|
||||
|
||||
public int getBatchSize() {
|
||||
return this.batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public long getIdleTimeout() {
|
||||
return this.idleTimeout;
|
||||
}
|
||||
|
||||
public void setIdleTimeout(long idleTimeout) {
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
Map<String, String> getColumnsMap() {
|
||||
if (this.columnsMap == null) {
|
||||
this.columnsMap = this.shorthandMapConverter.convert(this.columns);
|
||||
}
|
||||
return this.columnsMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A Converter from String to Map that accepts csv {@literal key:value} pairs
|
||||
* (similar to what comes out of the box in Spring Core) but also simple
|
||||
* {@literal key} items, in which case the value is assumed to be equal to the key.
|
||||
* <p>
|
||||
* <p>Additionally, commas and colons can be escaped by using a backslash, which is
|
||||
* useful if said mappings are to be used for SpEL for example.</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class ShorthandMapConverter implements Converter<String, Map<String, String>> {
|
||||
|
||||
@Override
|
||||
public Map<String, String> convert(String source) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
|
||||
// Split on comma if not preceded by backslash
|
||||
String[] mappings = source.split("(?<!\\\\),");
|
||||
for (String mapping : mappings) {
|
||||
// Turn backslash-comma back to comma
|
||||
String unescaped = mapping.trim().replace("\\,", ",");
|
||||
if (unescaped.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
// Split on colon, if not preceded by backslash
|
||||
String[] keyValuePair = unescaped.split("(?<!\\\\):");
|
||||
Assert.isTrue(keyValuePair.length <= 2, "'" + unescaped +
|
||||
"' could not be parsed to a 'key:value' pair or simple 'key' with implicit value");
|
||||
String key = keyValuePair[0].trim().replace("\\:", ":");
|
||||
String value = keyValuePair.length == 2 ? keyValuePair[1].trim().replace("\\:", ":") : key;
|
||||
result.put(key, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = { "jdbc.consumer.batchSize=1000", "jdbc.consumer.idleTimeout=100" })
|
||||
public class BatchInsertTimeoutTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testBatchInsertionTimeout() {
|
||||
final int numberOfInserts = 10;
|
||||
Payload sent = new Payload("hello", 42);
|
||||
for (int i = 0; i < numberOfInserts; i++) {
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
}
|
||||
Awaitility.await().until(() -> jdbcOperations
|
||||
.queryForObject("select count(*) from messages", Integer.class), value -> value == numberOfInserts);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a")
|
||||
public class DataReceivedAsByteArrayTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertionWhenDataReceivedAsByteArray() {
|
||||
String hello = "{\"a\": \"hello\"}";
|
||||
final Message<byte[]> message = MessageBuilder.withPayload(hello.getBytes()).build();
|
||||
jdbcConsumer.accept(message);
|
||||
final Integer count =
|
||||
jdbcOperations.queryForObject("select count(*) from messages where a = ?", Integer.class, "hello");
|
||||
assertThat(count).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = { "jdbc.consumer.tableName=foobar",
|
||||
"jdbc.consumer.initialize=classpath:explicit-script.sql",
|
||||
"jdbc.consumer.columns=a,b" })
|
||||
public class ExplicitTableCreationTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
Payload sent = new Payload("hello", 42);
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
Payload result =
|
||||
jdbcOperations.query("select a, b from foobar", new BeanPropertyRowMapper<>(Payload.class))
|
||||
.get(0);
|
||||
assertThat(result).isEqualToComparingFieldByField(sent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a: headers[foo]")
|
||||
public class HeaderInsertTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testHeaderInsertion() {
|
||||
Payload sent = new Payload("hello", 42);
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent)
|
||||
.setHeader("foo", "bar").build();
|
||||
jdbcConsumer.accept(message);
|
||||
assertThat(jdbcOperations.queryForObject("select count(*) from messages where a = ?",
|
||||
Integer.class, "bar")).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = {
|
||||
"jdbc.consumer.tableName=no_script",
|
||||
"jdbc.consumer.initialize=true",
|
||||
"jdbc.consumer.columns=a,b" })
|
||||
public class ImplicitTableCreationTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
Payload sent = new Payload("hello", 42);
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
Payload result = jdbcOperations
|
||||
.query("select a, b from no_script", new BeanPropertyRowMapper<>(Payload.class)).get(0);
|
||||
assertThat(result).isEqualToComparingFieldByField(sent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
/**
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@SpringBootTest
|
||||
@DirtiesContext
|
||||
public class JdbcConsumerApplicationTests {
|
||||
|
||||
@Autowired
|
||||
Consumer<Message<?>> jdbcConsumer;
|
||||
|
||||
@Autowired
|
||||
JdbcOperations jdbcOperations;
|
||||
|
||||
@Autowired
|
||||
JdbcTemplate jdbcTemplate;
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
jdbcOperations.execute("DROP TABLE MESSAGES IF EXISTS");
|
||||
}
|
||||
|
||||
static class Payload {
|
||||
|
||||
private String a;
|
||||
|
||||
private Integer b;
|
||||
|
||||
public Payload() {
|
||||
}
|
||||
|
||||
public Payload(String a, Integer b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public String getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
public Integer getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
public void setA(String a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public void setB(Integer b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a,b")
|
||||
public class JsonStringPayloadInsertTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
String stringA = "{\"a\": \"hello1\", \"b\": 42}";
|
||||
String stringB = "{\"a\": \"hello2\", \"b\": null}";
|
||||
String stringC = "{\"a\": \"hello3\"}";
|
||||
final Message<String> message1 = MessageBuilder.withPayload(stringA).build();
|
||||
jdbcConsumer.accept(message1);
|
||||
final Message<String> message2 = MessageBuilder.withPayload(stringB).build();
|
||||
jdbcConsumer.accept(message2);
|
||||
final Message<String> message3 = MessageBuilder.withPayload(stringC).build();
|
||||
jdbcConsumer.accept(message3);
|
||||
assertThat(jdbcOperations.queryForObject(
|
||||
"select count(*) from messages where a = ? and b = ?",
|
||||
Integer.class, "hello1", 42)).isEqualTo(1);
|
||||
assertThat(jdbcOperations.queryForObject(
|
||||
"select count(*) from messages where a = ? and b IS NULL",
|
||||
Integer.class, "hello2")).isEqualTo(1);
|
||||
assertThat(jdbcOperations.queryForObject(
|
||||
"select count(*) from messages where a = ? and b IS NULL",
|
||||
Integer.class, "hello3")).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a,b")
|
||||
public class MapPayloadInsertTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
NamedParameterJdbcOperations namedParameterJdbcOperations = new NamedParameterJdbcTemplate(jdbcOperations);
|
||||
Map<String, Object> mapA = new HashMap<>();
|
||||
mapA.put("a", "hello1");
|
||||
mapA.put("b", 42);
|
||||
Map<String, Object> mapB = new HashMap<>();
|
||||
mapB.put("a", "hello2");
|
||||
mapB.put("b", null);
|
||||
Map<String, Object> mapC = new HashMap<>();
|
||||
mapC.put("a", "hello3");
|
||||
final Message<Map<String, Object>> message1 = MessageBuilder.withPayload(mapA).build();
|
||||
jdbcConsumer.accept(message1);
|
||||
final Message<Map<String, Object>> message2 = MessageBuilder.withPayload(mapB).build();
|
||||
jdbcConsumer.accept(message2);
|
||||
final Message<Map<String, Object>> message3 = MessageBuilder.withPayload(mapC).build();
|
||||
jdbcConsumer.accept(message3);
|
||||
assertThat(namedParameterJdbcOperations.queryForObject(
|
||||
"select count(*) from messages where a = :a and b = :b", mapA, Integer.class)).isEqualTo(1);
|
||||
assertThat(namedParameterJdbcOperations.queryForObject(
|
||||
"select count(*) from messages where a = :a and b IS NULL", mapB, Integer.class)).isEqualTo(1);
|
||||
assertThat(namedParameterJdbcOperations.queryForObject(
|
||||
"select count(*) from messages where a = :a and b IS NULL", mapC, Integer.class)).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.batchSize=1000")
|
||||
public class SimpleBatchInsertTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testBatchInsertion() {
|
||||
final int numberOfInserts = 5000;
|
||||
Payload sent = new Payload("hello", 42);
|
||||
for (int i = 0; i < numberOfInserts; i++) {
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
}
|
||||
int result = jdbcOperations.queryForObject("select count(*) from messages", Integer.class);
|
||||
assertThat(result).isEqualTo(numberOfInserts);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
public class SimpleInsertTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleInsert() {
|
||||
Payload sent = new Payload("hello", 42);
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
String result = jdbcOperations.queryForObject("select payload from messages", String.class);
|
||||
assertThat(result).isEqualTo(("hello42"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a,b")
|
||||
public class SimpleMappingTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
Payload sent = new Payload("hello", 42);
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
Payload result = jdbcOperations
|
||||
.query("select a, b from messages", new BeanPropertyRowMapper<>(Payload.class)).get(0);
|
||||
assertThat(result).isEqualToComparingFieldByField(sent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
// annotation below relies on java.util.Properties so backslash needs to be doubled
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a: a.substring(0\\\\, 4), b: b + 624")
|
||||
public class SpELTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
Payload sent = new Payload("hello", 42);
|
||||
final Message<Payload> message = MessageBuilder.withPayload(sent).build();
|
||||
jdbcConsumer.accept(message);
|
||||
Payload expected = new Payload("hell", 666);
|
||||
Payload result = jdbcOperations
|
||||
.query("select a, b from messages", new BeanPropertyRowMapper<>(Payload.class)).get(0);
|
||||
assertThat(result).isEqualToComparingFieldByField(expected);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a: new StringBuilder(payload.a).reverse().toString(), b")
|
||||
public class UnqualifiableColumnExpressionTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void doesNotFailParsingUnqualifiableExpression() {
|
||||
// if the app initializes, the test condition passes, but go ahead and apply the column expression anyway
|
||||
jdbcConsumer.accept(MessageBuilder.withPayload(new Payload("desrever", 123)).build());
|
||||
assertThat(jdbcOperations.queryForObject("select count(*) from messages where a = ? and b = ?",
|
||||
Integer.class, "reversed", 123)).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.consumer.jdbc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* @author Eric Bottard
|
||||
* @author Thomas Risberg
|
||||
* @author Artem Bilan
|
||||
* @author Robert St. John
|
||||
* @author Oliver Flasch
|
||||
* @author Soby Chacko
|
||||
* @author Szabolcs Stremler
|
||||
*/
|
||||
@TestPropertySource(properties = "jdbc.consumer.columns=a,b")
|
||||
public class VaryingInsertTests extends JdbcConsumerApplicationTests {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
Payload a = new Payload("hello", 42);
|
||||
Payload b = new Payload("world", 12);
|
||||
Payload c = new Payload("bonjour", null);
|
||||
Payload d = new Payload(null, 22);
|
||||
final Message<Payload> message1 = MessageBuilder.withPayload(a).build();
|
||||
jdbcConsumer.accept(message1);
|
||||
final Message<Payload> message2 = MessageBuilder.withPayload(b).build();
|
||||
jdbcConsumer.accept(message2);
|
||||
final Message<Payload> message3 = MessageBuilder.withPayload(c).build();
|
||||
jdbcConsumer.accept(message3);
|
||||
final Message<Payload> message4 = MessageBuilder.withPayload(d).build();
|
||||
jdbcConsumer.accept(message4);
|
||||
List<Payload> result = jdbcOperations
|
||||
.query("select a, b from messages", new BeanPropertyRowMapper<>(Payload.class));
|
||||
Assertions.assertThat(result).extracting("a").containsExactly("hello", "world", "bonjour", null);
|
||||
Assertions.assertThat(result).extracting("b").contains(42, 12, 22, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Used in test for explicit script
|
||||
|
||||
create table foobar(
|
||||
a varchar(2000),
|
||||
b VARCHAR (2000)
|
||||
);
|
||||
7
consumer/jdbc-consumer/src/test/resources/schema.sql
Normal file
7
consumer/jdbc-consumer/src/test/resources/schema.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
-- Run by default by Boot infrastructure
|
||||
|
||||
create table messages(
|
||||
a varchar(2000),
|
||||
b VARCHAR (2000),
|
||||
payload VARCHAR (2000)
|
||||
);
|
||||
31
consumer/log-consumer/.gitignore
vendored
Normal file
31
consumer/log-consumer/.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
59
consumer/log-consumer/pom.xml
Normal file
59
consumer/log-consumer/pom.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>log-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>log-consumer</name>
|
||||
<description>Log Consumer</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.pivotal.java.function</groupId>
|
||||
<artifactId>payload-converter-function</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.log;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The Configuration class for {@link Consumer} which logs incoming data.
|
||||
* For the logging logic a Spring Integration {@link org.springframework.integration.handler.LoggingHandler}
|
||||
* is used.
|
||||
* If incoming payload is a {@code byte[]} and incoming message {@code contentType} header is text-compatible
|
||||
* (e.g. {@code application/json}), it is converted into a {@link String}.
|
||||
* Otherwise the payload is passed to logger as is.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(LogConsumerProperties.class)
|
||||
public class LogConsumerConfiguration {
|
||||
|
||||
@Bean
|
||||
IntegrationFlow logConsumerFlow(LogConsumerProperties logSinkProperties) {
|
||||
return IntegrationFlows.from(MessageConsumer.class, (gateway) -> gateway.beanName("logConsumer"))
|
||||
.handle((payload, headers) -> payload)
|
||||
.log(logSinkProperties.getLevel(), logSinkProperties.getName(), logSinkProperties.getExpression())
|
||||
.get();
|
||||
}
|
||||
|
||||
private interface MessageConsumer extends Consumer<Message<?>> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.log;
|
||||
|
||||
import static org.springframework.integration.handler.LoggingHandler.Level.INFO;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.integration.handler.LoggingHandler;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Configuration properties for the Log Sink app.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Eric Bottard
|
||||
* @author Chris Schaefer
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ConfigurationProperties("log")
|
||||
@Validated
|
||||
public class LogConsumerProperties {
|
||||
|
||||
/**
|
||||
* The name of the logger to use.
|
||||
*/
|
||||
@Value("${spring.application.name:log.consumer}")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* A SpEL expression (against the incoming message) to evaluate as the logged message.
|
||||
*/
|
||||
private String expression = "payload";
|
||||
|
||||
/**
|
||||
* The level at which to log messages.
|
||||
*/
|
||||
private LoggingHandler.Level level = INFO;
|
||||
|
||||
@NotBlank
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@NotBlank
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
public void setExpression(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LoggingHandler.Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(LoggingHandler.Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.consumer.log;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.integration.handler.LoggingHandler;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
@SpringBootTest({ "log.name=foo", "log.level=warn", "log.expression=payload.toUpperCase()" })
|
||||
class LogConsumerApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private Consumer<Message<?>> logConsumer;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("logConsumerFlow.logging-channel-adapter#0")
|
||||
private LoggingHandler loggingHandler;
|
||||
|
||||
@Test
|
||||
public void testJsonContentType() {
|
||||
Message<String> message = MessageBuilder.withPayload("{\"foo\":\"bar\"}")
|
||||
.setHeader("contentType", new MimeType("json"))
|
||||
.build();
|
||||
testMessage(message, "{\"foo\":\"bar\"}");
|
||||
}
|
||||
|
||||
private void testMessage(Message<?> message, String expectedPayload) {
|
||||
assertThat(this.loggingHandler.getLevel()).isEqualTo(LoggingHandler.Level.WARN);
|
||||
Log logger = TestUtils.getPropertyValue(this.loggingHandler, "messageLogger", Log.class);
|
||||
assertThat(TestUtils.getPropertyValue(logger, "logger.name")).isEqualTo("foo");
|
||||
logger = spy(logger);
|
||||
new DirectFieldAccessor(this.loggingHandler).setPropertyValue("messageLogger", logger);
|
||||
this.logConsumer.accept(message);
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(logger).warn(captor.capture());
|
||||
assertThat(captor.getValue()).isEqualTo(expectedPayload.toUpperCase());
|
||||
this.loggingHandler.setLogExpressionString("#this");
|
||||
this.logConsumer.accept(message);
|
||||
verify(logger, times(2)).warn(captor.capture());
|
||||
|
||||
Message<?> captorMessage = (Message<?>) captor.getAllValues().get(2);
|
||||
assertThat(captorMessage.getPayload()).isEqualTo(expectedPayload);
|
||||
|
||||
MessageHeaders messageHeaders = captorMessage.getHeaders();
|
||||
assertThat(messageHeaders).hasSize(3);
|
||||
|
||||
assertThat(messageHeaders)
|
||||
.containsEntry(MessageHeaders.CONTENT_TYPE, message.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
}
|
||||
31
consumer/mongodb-consumer/.gitignore
vendored
Normal file
31
consumer/mongodb-consumer/.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
58
consumer/mongodb-consumer/pom.xml
Normal file
58
consumer/mongodb-consumer/pom.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>mongodb-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>mongodb-consumer</name>
|
||||
<description>Mongo DB consumer</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-mongodb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-reactivestreams</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2017-2020 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.cloud.fn.consumer.mongo;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.mongodb.outbound.ReactiveMongoDbStoringMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.ReactiveMessageHandler;
|
||||
|
||||
/**
|
||||
* A configuration for MongoDB Consumer function. Uses a
|
||||
* {@link ReactiveMongoDbStoringMessageHandler} to save payload contents to Mongo DB.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author David Turanski
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ MongoDbConsumerProperties.class })
|
||||
public class MongoDbConsumerConfiguration {
|
||||
|
||||
private final MongoDbConsumerProperties properties;
|
||||
|
||||
private final ReactiveMongoTemplate mongoTemplate;
|
||||
|
||||
public MongoDbConsumerConfiguration(MongoDbConsumerProperties properties, ReactiveMongoTemplate mongoTemplate) {
|
||||
this.properties = properties;
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<Message<?>, Mono<Void>> mongodbConsumer(ReactiveMessageHandler mongoConsumerMessageHandler) {
|
||||
return mongoConsumerMessageHandler::handleMessage;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveMessageHandler mongoConsumerMessageHandler() {
|
||||
ReactiveMongoDbStoringMessageHandler mongoDbMessageHandler = new ReactiveMongoDbStoringMessageHandler(
|
||||
this.mongoTemplate);
|
||||
Expression collectionExpression = this.properties.getCollectionExpression();
|
||||
if (collectionExpression == null) {
|
||||
collectionExpression = new LiteralExpression(this.properties.getCollection());
|
||||
}
|
||||
mongoDbMessageHandler.setCollectionNameExpression(collectionExpression);
|
||||
return mongoDbMessageHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.mongo;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author David Turanski
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("mongodb.consumer")
|
||||
@Validated
|
||||
public class MongoDbConsumerProperties {
|
||||
|
||||
/**
|
||||
* The MongoDB collection to store data
|
||||
*/
|
||||
private String collection;
|
||||
|
||||
/**
|
||||
* The SpEL expression to evaluate MongoDB collection
|
||||
*/
|
||||
private Expression collectionExpression;
|
||||
|
||||
public void setCollection(String collection) {
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
public String getCollection() {
|
||||
return this.collection;
|
||||
}
|
||||
|
||||
public void setCollectionExpression(Expression collectionExpression) {
|
||||
this.collectionExpression = collectionExpression;
|
||||
}
|
||||
|
||||
public Expression getCollectionExpression() {
|
||||
return collectionExpression;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "One of 'collection' or 'collectionExpression' is required")
|
||||
private boolean isValid() {
|
||||
return StringUtils.hasText(this.collection) || this.collectionExpression != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.mongo;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.function.Function;
|
||||
import org.bson.Document;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*/
|
||||
@SpringBootTest(properties = {
|
||||
"spring.data.mongodb.port=0",
|
||||
"mongodb.consumer.collection=testing"})
|
||||
class MongoDbConsumerApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private MongoDbConsumerProperties properties;
|
||||
|
||||
@Autowired
|
||||
private Function<Message<?>, Mono<Void>> mongoDbConsumer;
|
||||
|
||||
@Autowired
|
||||
private ReactiveMongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
void testMongodbConsumer() {
|
||||
Map<String, String> data1 = new HashMap<>();
|
||||
data1.put("foo", "bar");
|
||||
|
||||
Map<String, String> data2 = new HashMap<>();
|
||||
data2.put("firstName", "Foo");
|
||||
data2.put("lastName", "Bar");
|
||||
|
||||
Flux<Message<?>> messages = Flux.just(
|
||||
new GenericMessage<>(data1),
|
||||
new GenericMessage<>(data2),
|
||||
new GenericMessage<>("{\"my_data\": \"THE DATA\"}")
|
||||
);
|
||||
|
||||
messages.flatMap(mongoDbConsumer::apply).blockLast(Duration.ofSeconds(10));
|
||||
|
||||
StepVerifier.create(this.mongoTemplate.findAll(Document.class, properties.getCollection())
|
||||
.sort(Comparator.comparing(d -> d.get("_id").toString())))
|
||||
.assertNext(document -> {
|
||||
assertThat(document.get("foo")).isEqualTo("bar");
|
||||
})
|
||||
.assertNext(document-> {
|
||||
assertThat(document.get("firstName")).isEqualTo("Foo");
|
||||
assertThat(document.get("lastName")).isEqualTo("Bar");
|
||||
})
|
||||
.assertNext(document-> {
|
||||
assertThat(document.get("my_data")).isEqualTo("THE DATA");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
}
|
||||
40
consumer/rabbit-consumer/pom.xml
Normal file
40
consumer/rabbit-consumer/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>rabbit-consumer</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>rabbit-consumer</name>
|
||||
<description>Rabbit consumer</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.rabbit;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.amqp.dsl.Amqp;
|
||||
import org.springframework.integration.amqp.dsl.AmqpOutboundChannelAdapterSpec;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
@EnableConfigurationProperties(RabbitConsumerProperties.class)
|
||||
@Configuration
|
||||
public class RabbitConsumerConfiguration implements DisposableBean {
|
||||
|
||||
@Autowired
|
||||
private RabbitProperties bootProperties;
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<ConnectionNameStrategy> connectionNameStrategy;
|
||||
|
||||
@Autowired
|
||||
private RabbitConsumerProperties properties;
|
||||
|
||||
@Value("#{${rabbit.converterBeanName:null}}")
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
private CachingConnectionFactory ownConnectionFactory;
|
||||
|
||||
@Bean
|
||||
public Function<Message<?>, Object> rabbitConsumer(@Qualifier("amqpChannelAdapter") MessageHandler messageHandler) {
|
||||
return o -> {
|
||||
messageHandler.handleMessage(o);
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageHandler amqpChannelAdapter(ConnectionFactory rabbitConnectionFactory)
|
||||
throws Exception {
|
||||
|
||||
AmqpOutboundChannelAdapterSpec handler = Amqp
|
||||
.outboundAdapter(rabbitTemplate(this.properties.isOwnConnection()
|
||||
? buildLocalConnectionFactory() : rabbitConnectionFactory))
|
||||
.mappedRequestHeaders(properties.getMappedRequestHeaders())
|
||||
.defaultDeliveryMode(properties.getPersistentDeliveryMode()
|
||||
? MessageDeliveryMode.PERSISTENT
|
||||
: MessageDeliveryMode.NON_PERSISTENT);
|
||||
|
||||
Expression exchangeExpression = this.properties.getExchangeExpression();
|
||||
if (exchangeExpression != null) {
|
||||
handler.exchangeNameExpression(exchangeExpression);
|
||||
}
|
||||
else {
|
||||
handler.exchangeName(this.properties.getExchange());
|
||||
}
|
||||
|
||||
Expression routingKeyExpression = this.properties.getRoutingKeyExpression();
|
||||
if (routingKeyExpression != null) {
|
||||
handler.routingKeyExpression(routingKeyExpression);
|
||||
}
|
||||
else {
|
||||
handler.routingKey(this.properties.getRoutingKey());
|
||||
}
|
||||
return handler.get();
|
||||
}
|
||||
|
||||
private ConnectionFactory buildLocalConnectionFactory() throws Exception {
|
||||
this.ownConnectionFactory = new AutoConfig.Creator().rabbitConnectionFactory(
|
||||
this.bootProperties, this.connectionNameStrategy);
|
||||
return this.ownConnectionFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory rabbitConnectionFactory) {
|
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory);
|
||||
if (this.messageConverter != null) {
|
||||
rabbitTemplate.setMessageConverter(this.messageConverter);
|
||||
}
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "rabbit.converterBeanName",
|
||||
havingValue = RabbitConsumerProperties.JSON_CONVERTER)
|
||||
public Jackson2JsonMessageConverter jsonConverter() {
|
||||
return new Jackson2JsonMessageConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (this.ownConnectionFactory != null) {
|
||||
this.ownConnectionFactory.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AutoConfig extends RabbitAutoConfiguration {
|
||||
|
||||
static class Creator extends RabbitConnectionFactoryCreator {
|
||||
|
||||
@Override
|
||||
public CachingConnectionFactory rabbitConnectionFactory(RabbitProperties config,
|
||||
ObjectProvider<ConnectionNameStrategy> connectionNameStrategy)
|
||||
throws Exception {
|
||||
CachingConnectionFactory cf = super.rabbitConnectionFactory(config,
|
||||
connectionNameStrategy);
|
||||
cf.setConnectionNameStrategy(
|
||||
connectionFactory -> "rabbit.sink.own.connection");
|
||||
cf.afterPropertiesSet();
|
||||
return cf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.cloud.fn.consumer.rabbit;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@ConfigurationProperties("rabbit")
|
||||
@Validated
|
||||
public class RabbitConsumerProperties {
|
||||
|
||||
public static final String JSON_CONVERTER = "jsonConverter";
|
||||
|
||||
/**
|
||||
* Exchange name - overridden by exchangeNameExpression, if supplied.
|
||||
*/
|
||||
private String exchange = "";
|
||||
|
||||
/**
|
||||
* A SpEL expression that evaluates to an exchange name.
|
||||
*/
|
||||
private Expression exchangeExpression;
|
||||
|
||||
/**
|
||||
* Routing key - overridden by routingKeyExpression, if supplied.
|
||||
*/
|
||||
private String routingKey;
|
||||
|
||||
/**
|
||||
* A SpEL expression that evaluates to a routing key.
|
||||
*/
|
||||
private Expression routingKeyExpression;
|
||||
|
||||
/**
|
||||
* Default delivery mode when 'amqp_deliveryMode' header is not present,
|
||||
* true for PERSISTENT.
|
||||
*/
|
||||
private boolean persistentDeliveryMode;
|
||||
|
||||
/**
|
||||
* Headers that will be mapped.
|
||||
*/
|
||||
private String[] mappedRequestHeaders = { "*" };
|
||||
|
||||
/**
|
||||
* The bean name for a custom message converter; if omitted, a SimpleMessageConverter is used.
|
||||
* If 'jsonConverter', a Jackson2JsonMessageConverter bean will be created for you.
|
||||
*/
|
||||
private String converterBeanName;
|
||||
|
||||
/**
|
||||
* When true, use a separate connection based on the boot properties.
|
||||
*/
|
||||
private boolean ownConnection;
|
||||
|
||||
public String getExchange() {
|
||||
return this.exchange;
|
||||
}
|
||||
|
||||
public void setExchange(String exchange) {
|
||||
this.exchange = exchange;
|
||||
}
|
||||
|
||||
public Expression getExchangeExpression() {
|
||||
return this.exchangeExpression;
|
||||
}
|
||||
|
||||
public void setExchangeExpression(Expression exchangeExpression) {
|
||||
this.exchangeExpression = exchangeExpression;
|
||||
}
|
||||
|
||||
public String getRoutingKey() {
|
||||
return this.routingKey;
|
||||
}
|
||||
|
||||
public void setRoutingKey(String routingKey) {
|
||||
this.routingKey = routingKey;
|
||||
}
|
||||
|
||||
public Expression getRoutingKeyExpression() {
|
||||
return this.routingKeyExpression;
|
||||
}
|
||||
|
||||
public void setRoutingKeyExpression(Expression routingKeyExpression) {
|
||||
this.routingKeyExpression = routingKeyExpression;
|
||||
}
|
||||
|
||||
public boolean getPersistentDeliveryMode() {
|
||||
return this.persistentDeliveryMode;
|
||||
}
|
||||
|
||||
public void setPersistentDeliveryMode(boolean persistentDeliveryMode) {
|
||||
this.persistentDeliveryMode = persistentDeliveryMode;
|
||||
}
|
||||
|
||||
public String[] getMappedRequestHeaders() {
|
||||
return this.mappedRequestHeaders;
|
||||
}
|
||||
|
||||
public void setMappedRequestHeaders(String[] mappedRequestHeaders) {
|
||||
this.mappedRequestHeaders = mappedRequestHeaders;
|
||||
}
|
||||
|
||||
public String getConverterBeanName() {
|
||||
return this.converterBeanName;
|
||||
}
|
||||
|
||||
public void setConverterBeanName(String converterBeanName) {
|
||||
this.converterBeanName = converterBeanName;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "routingKey or routingKeyExpression is required")
|
||||
public boolean isRoutingKeyProvided() {
|
||||
return this.routingKey != null || this.routingKeyExpression != null;
|
||||
}
|
||||
|
||||
public boolean isOwnConnection() {
|
||||
return this.ownConnection;
|
||||
}
|
||||
|
||||
public void setOwnConnection(boolean ownConnection) {
|
||||
this.ownConnection = ownConnection;
|
||||
}
|
||||
|
||||
}
|
||||
28
function/filter-function/.gitignore
vendored
Normal file
28
function/filter-function/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
36
function/filter-function/pom.xml
Normal file
36
function/filter-function/pom.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>filter-function</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>filter-function</name>
|
||||
<description>Spring Native Function for applying filter SpEL expressions</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spel-function</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.filter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cloud.fn.spel.SpelFunctionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
@Configuration
|
||||
@Import(SpelFunctionConfiguration.class)
|
||||
public class FilterFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<Message<?>, Message<?>> filterFunction(
|
||||
@Qualifier("spelFunction") Function<Message<?>, Message<?>> spelFunction) {
|
||||
|
||||
return message ->
|
||||
Optional.of(message)
|
||||
.filter(m -> (Boolean) spelFunction.apply(m).getPayload())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
spel.function.expression=true
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.filter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
@SpringBootTest(properties = "spel.function.expression=payload.length() > 5")
|
||||
@DirtiesContext
|
||||
public class FilterFunctionApplicationTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("filterFunction")
|
||||
Function<Message<?>, Message<?>> filter;
|
||||
|
||||
@Test
|
||||
public void testFilter() {
|
||||
Message<?> filtered = this.filter.apply(new GenericMessage<>("hello"));
|
||||
assertThat(filtered).isNull();
|
||||
filtered = this.filter.apply(new GenericMessage<>("hello world"));
|
||||
assertThat(filtered).isNotNull()
|
||||
.extracting(Message::getPayload)
|
||||
.isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
}
|
||||
37
function/payload-converter-function/pom.xml
Normal file
37
function/payload-converter-function/pom.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>payload-converter-function</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>payload-converter-function</name>
|
||||
<description>Utility message conversion functions</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-messaging</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 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 functions;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class ByteArrayTextToString implements Function<Message<?>, Message<?>> {
|
||||
|
||||
@Override
|
||||
public Message<?> apply(Message<?> message) {
|
||||
|
||||
if (message.getPayload() instanceof byte[]) {
|
||||
final MessageHeaders headers = message.getHeaders();
|
||||
String contentType = headers.containsKey(MessageHeaders.CONTENT_TYPE)
|
||||
? headers.get(MessageHeaders.CONTENT_TYPE).toString()
|
||||
: MimeTypeUtils.APPLICATION_JSON_VALUE;
|
||||
|
||||
if (contentType.contains("text") || contentType.contains("json") || contentType.contains("x-spring-tuple")) {
|
||||
message = MessageBuilder.withPayload(new String(((byte[]) message.getPayload())))
|
||||
.copyHeaders(message.getHeaders())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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 functions;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ByteArrayTextToStringTests {
|
||||
|
||||
private static final String MESSAGE = "hello world";
|
||||
private static Function<Message<?>, Message<?>> converter;
|
||||
|
||||
@BeforeAll
|
||||
static void before() {
|
||||
converter = new ByteArrayTextToString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultNoContentType() {
|
||||
Message<?> converted = converter.apply(new GenericMessage<>(MESSAGE.getBytes()));
|
||||
assertThat(converted).isNotNull().extracting(Message::getPayload).isEqualTo(MESSAGE);
|
||||
|
||||
converted = converter.apply(new GenericMessage<>(MESSAGE)); // String
|
||||
assertThat(converted).isNotNull().extracting(Message::getPayload).isEqualTo(MESSAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplicationJsonContentType() {
|
||||
Message<?> converted = converter.apply(new GenericMessage<>(MESSAGE.getBytes(),
|
||||
Collections.singletonMap(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE)));
|
||||
assertThat(converted).isNotNull().extracting(Message::getPayload).isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlainTextContentType() {
|
||||
Message<?> converted = converter.apply(new GenericMessage<>(MESSAGE.getBytes(),
|
||||
Collections.singletonMap(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN_VALUE)));
|
||||
assertThat(converted).isNotNull().extracting(Message::getPayload).isEqualTo(MESSAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOctetContentType() {
|
||||
Message<?> converted = converter.apply(new GenericMessage<>(MESSAGE.getBytes(),
|
||||
Collections.singletonMap(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE)));
|
||||
assertThat(converted).isNotNull().extracting(Message::getPayload).isEqualTo(MESSAGE.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomNonTextContentType() {
|
||||
Message<?> converted = converter.apply(new GenericMessage<>(MESSAGE.getBytes(),
|
||||
Collections.singletonMap(MessageHeaders.CONTENT_TYPE, "Random Content Type")));
|
||||
assertThat(converted).isNotNull().extracting(Message::getPayload).isEqualTo(MESSAGE.getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
28
function/spel-function/.gitignore
vendored
Normal file
28
function/spel-function/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
46
function/spel-function/pom.xml
Normal file
46
function/spel-function/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spel-function</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>spel-function</name>
|
||||
<description>Spring Native Function for applying SpEL expressions</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud.function</groupId>
|
||||
<artifactId>payload-converter-function</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.spel;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.transformer.ExpressionEvaluatingTransformer;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(SpelFunctionProperties.class)
|
||||
public class SpelFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<Message<?>, Message<?>> spelFunction(
|
||||
ExpressionEvaluatingTransformer expressionEvaluatingTransformer) {
|
||||
|
||||
return message -> expressionEvaluatingTransformer.transform(message);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExpressionEvaluatingTransformer expressionEvaluatingTransformer(
|
||||
SpelFunctionProperties spelFunctionProperties) {
|
||||
|
||||
return new ExpressionEvaluatingTransformer(new SpelExpressionParser()
|
||||
.parseExpression(spelFunctionProperties.getExpression()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.spel;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* Configuration properties for the SpEL function.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ConfigurationProperties("spel.function")
|
||||
public class SpelFunctionProperties {
|
||||
|
||||
private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload");
|
||||
|
||||
/**
|
||||
* A SpEL expression to apply.
|
||||
*/
|
||||
private String expression = DEFAULT_EXPRESSION.getExpressionString();
|
||||
|
||||
public void setExpression(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public String getExpression() {
|
||||
return this.expression;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.spel;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(properties = "spel.function.expression=payload.toUpperCase()")
|
||||
@DirtiesContext
|
||||
public class SpelFunctionApplicationTests {
|
||||
|
||||
@Autowired
|
||||
Function<Message<?>, Message<?>> transformer;
|
||||
|
||||
@Test
|
||||
public void testTransform() {
|
||||
final Message<?> transformed = this.transformer.apply(new GenericMessage<>("hello,world"));
|
||||
assertThat(transformed.getPayload()).isEqualTo("HELLO,WORLD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJson() {
|
||||
Message<?> message = MessageBuilder.withPayload("{\"foo\":\"bar\"}")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();
|
||||
final Message<?> transformed = this.transformer.apply(message);
|
||||
assertThat(transformed.getPayload()).isEqualTo("{\"FOO\":\"BAR\"}");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {
|
||||
|
||||
}
|
||||
}
|
||||
28
function/splitter-function/.gitignore
vendored
Normal file
28
function/splitter-function/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
50
function/splitter-function/pom.xml
Normal file
50
function/splitter-function/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>splitter-function</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>splitter-function</name>
|
||||
<description>Spring Native Function for Splitter</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-file</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.splitter;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel;
|
||||
import org.springframework.integration.file.splitter.FileSplitter;
|
||||
import org.springframework.integration.splitter.AbstractMessageSplitter;
|
||||
import org.springframework.integration.splitter.DefaultMessageSplitter;
|
||||
import org.springframework.integration.splitter.ExpressionEvaluatingSplitter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(SplitterFunctionProperties.class)
|
||||
public class SplitterFunctionConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<Message<?>, List<Message<?>>> splitterFunction(AbstractMessageSplitter messageSplitter,
|
||||
SplitterFunctionProperties splitterFunctionProperties) {
|
||||
|
||||
messageSplitter.setApplySequence(splitterFunctionProperties.isApplySequence());
|
||||
ThreadLocalFluxSinkMessageChannel outputChannel = new ThreadLocalFluxSinkMessageChannel();
|
||||
messageSplitter.setOutputChannel(outputChannel);
|
||||
return message -> {
|
||||
messageSplitter.handleMessage(message);
|
||||
return outputChannel.publisherThreadLocal.get();
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "splitter", name = "expression")
|
||||
public AbstractMessageSplitter expressionSplitter(SplitterFunctionProperties splitterFunctionProperties) {
|
||||
return new ExpressionEvaluatingSplitter(
|
||||
new SpelExpressionParser()
|
||||
.parseExpression(splitterFunctionProperties.getExpression()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(FileSplitterCondition.class)
|
||||
public AbstractMessageSplitter fileSplitter(SplitterFunctionProperties splitterFunctionProperties) {
|
||||
Boolean markers = splitterFunctionProperties.getFileMarkers();
|
||||
String charset = splitterFunctionProperties.getCharset();
|
||||
if (markers == null) {
|
||||
markers = false;
|
||||
}
|
||||
FileSplitter fileSplitter = new FileSplitter(true, markers, splitterFunctionProperties.getMarkersJson());
|
||||
if (charset != null) {
|
||||
fileSplitter.setCharset(Charset.forName(charset));
|
||||
}
|
||||
return fileSplitter;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AbstractMessageSplitter defaultSplitter(SplitterFunctionProperties splitterFunctionProperties) {
|
||||
DefaultMessageSplitter defaultMessageSplitter = new DefaultMessageSplitter();
|
||||
defaultMessageSplitter.setDelimiters(splitterFunctionProperties.getDelimiters());
|
||||
return defaultMessageSplitter;
|
||||
}
|
||||
|
||||
static class FileSplitterCondition extends AnyNestedCondition {
|
||||
|
||||
FileSplitterCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "splitter", name = "charset")
|
||||
static class Charset { }
|
||||
|
||||
@ConditionalOnProperty(prefix = "splitter", name = "fileMarkers")
|
||||
static class FileMarkers { }
|
||||
|
||||
}
|
||||
|
||||
private static final class ThreadLocalFluxSinkMessageChannel
|
||||
implements MessageChannel, ReactiveStreamsSubscribableChannel {
|
||||
|
||||
private final ThreadLocal<List<Message<?>>> publisherThreadLocal = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void subscribeTo(Publisher<? extends Message<?>> publisher) {
|
||||
this.publisherThreadLocal.set(Flux.from(publisher).collectList().cast(List.class).block());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(Message<?> message, long l) {
|
||||
throw new UnsupportedOperationException("This channel only supports a reactive 'subscribeTo()' ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* 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 org.springframework.cloud.fn.splitter;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Configuration properties for the Splitter Processor app.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ConfigurationProperties("splitter")
|
||||
@Validated
|
||||
public class SplitterFunctionProperties {
|
||||
|
||||
/**
|
||||
* A SpEL expression for splitting payloads.
|
||||
*/
|
||||
private String expression;
|
||||
|
||||
/**
|
||||
* When expression is null, delimiters to use when tokenizing
|
||||
* {@link String} payloads.
|
||||
*/
|
||||
private String delimiters;
|
||||
|
||||
/**
|
||||
* Set to true or false to use a {@code FileSplitter} (to split
|
||||
* text-based files by line) that includes
|
||||
* (or not) beginning/end of file markers.
|
||||
*/
|
||||
private Boolean fileMarkers;
|
||||
|
||||
/**
|
||||
* When 'fileMarkers == true', specify if they should be produced
|
||||
* as FileSplitter.FileMarker objects or JSON.
|
||||
*/
|
||||
private boolean markersJson = true;
|
||||
|
||||
/**
|
||||
* The charset to use when converting bytes in text-based files
|
||||
* to String.
|
||||
*/
|
||||
private String charset;
|
||||
|
||||
/**
|
||||
* Add correlation/sequence information in headers to facilitate later
|
||||
* aggregation.
|
||||
*/
|
||||
private boolean applySequence = true;
|
||||
|
||||
public void setExpression(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public String getExpression() {
|
||||
return this.expression;
|
||||
}
|
||||
|
||||
public String getDelimiters() {
|
||||
return this.delimiters;
|
||||
}
|
||||
|
||||
public void setDelimiters(String delimiters) {
|
||||
this.delimiters = delimiters;
|
||||
}
|
||||
|
||||
public Boolean getFileMarkers() {
|
||||
return this.fileMarkers;
|
||||
}
|
||||
|
||||
public void setFileMarkers(Boolean fileMarkers) {
|
||||
this.fileMarkers = fileMarkers;
|
||||
}
|
||||
|
||||
public boolean getMarkersJson() {
|
||||
return this.markersJson;
|
||||
}
|
||||
|
||||
public void setMarkersJson(boolean markersJson) {
|
||||
this.markersJson = markersJson;
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return this.charset;
|
||||
}
|
||||
|
||||
public void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public boolean isApplySequence() {
|
||||
return this.applySequence;
|
||||
}
|
||||
|
||||
public void setApplySequence(boolean applySequence) {
|
||||
this.applySequence = applySequence;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "'delimiters' is not allowed when an 'expression' is provided")
|
||||
public boolean isDelimitersAllowed() {
|
||||
return this.expression == null || this.delimiters == null;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "File properties are not allowed when an 'expression' or 'delimiters' property is provided")
|
||||
public boolean isFilePropsAllowed() {
|
||||
return !(this.expression != null || this.delimiters != null) || this.fileMarkers == null && this.charset == null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2020 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* 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.cloud.fn.splitter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
@SpringBootTest(properties = "splitter.expression=payload.split(',')")
|
||||
@DirtiesContext
|
||||
public class SplitterFunctionApplicationTests {
|
||||
|
||||
@Autowired
|
||||
Function<Message<?>, List<Message<?>>> splitter;
|
||||
|
||||
@Test
|
||||
public void testExpressionSplitter() {
|
||||
List<Message<?>> messageList = this.splitter.apply(new GenericMessage<>("hello,world"));
|
||||
assertThat(messageList).extracting(m -> m.getPayload().toString()).contains("hello", "world");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {}
|
||||
}
|
||||
205
pom.xml
Normal file
205
pom.xml
Normal file
@@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>java-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>java-functions-parent</name>
|
||||
<description>Pivotal Java Functions Parent</description>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-javadoc-plugin.version>3.1.1</maven-javadoc-plugin.version>
|
||||
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
|
||||
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>consumer/cassandra-consumer</module>
|
||||
<module>consumer/counter-consumer</module>
|
||||
<module>consumer/file-consumer</module>
|
||||
<module>consumer/jdbc-consumer</module>
|
||||
<module>consumer/log-consumer</module>
|
||||
<module>consumer/mongodb-consumer</module>
|
||||
<module>consumer/rabbit-consumer</module>
|
||||
|
||||
<module>function/filter-function</module>
|
||||
<module>function/spel-function</module>
|
||||
<module>function/payload-converter-function</module>
|
||||
<module>function/splitter-function</module>
|
||||
|
||||
<module>supplier/file-supplier</module>
|
||||
<module>supplier/http-supplier</module>
|
||||
<module>supplier/jdbc-supplier</module>
|
||||
<module>supplier/mongodb-supplier</module>
|
||||
<module>supplier/time-supplier</module>
|
||||
|
||||
<module>spring-functions-parent</module>
|
||||
</modules>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<comments>Copyright 2014-2020 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
|
||||
|
||||
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.</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/pivotal/java-functions.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/pivotal/java-functions.git</developerConnection>
|
||||
<url>https://github.com/pivotal/java-functions</url>
|
||||
</scm>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>${maven-javadoc-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>javadoc</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<quiet>true</quiet>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${maven-source-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
<include>**/*Test.java</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>**/Abstract*.java</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Release Repository</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>milestone</id>
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Milestone Repository</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>central</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>sonatype-nexus-staging</id>
|
||||
<name>Nexus Release Repository</name>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
40
spring-functions-parent/pom.xml
Normal file
40
spring-functions-parent/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-functions-parent</artifactId>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>2.3.0.M4</spring-boot.version>
|
||||
<spring-cloud-function.version>3.0.3.RELEASE</spring-cloud-function.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-dependencies</artifactId>
|
||||
<version>${spring-cloud-function.version}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
51
supplier/file-supplier/pom.xml
Normal file
51
supplier/file-supplier/pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>file-supplier</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>file-supplier</name>
|
||||
<description>file supplier</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud.fn</groupId>
|
||||
<artifactId>spring-functions-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../../spring-functions-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-file</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-integration</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-json</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.supplier.file;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* TODO: This will be used in other apps like (S)FTP and S3. Therefore, it might be moved to a common place.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ConfigurationProperties("file.consumer")
|
||||
@Validated
|
||||
public class FileConsumerProperties {
|
||||
|
||||
/**
|
||||
* The FileReadingMode to use for file reading sources.
|
||||
* Values are 'ref' - The File object,
|
||||
* 'lines' - a message per line, or
|
||||
* 'contents' - the contents as bytes.
|
||||
*/
|
||||
private FileReadingMode mode = FileReadingMode.contents;
|
||||
|
||||
/**
|
||||
* Set to true to emit start of file/end of file marker messages before/after the data.
|
||||
* Only valid with FileReadingMode 'lines'.
|
||||
*/
|
||||
private Boolean withMarkers = null;
|
||||
|
||||
/**
|
||||
* When 'fileMarkers == true', specify if they should be produced
|
||||
* as FileSplitter.FileMarker objects or JSON.
|
||||
*/
|
||||
private boolean markersJson = true;
|
||||
|
||||
@NotNull
|
||||
public FileReadingMode getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(FileReadingMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Boolean getWithMarkers() {
|
||||
return this.withMarkers;
|
||||
}
|
||||
|
||||
public void setWithMarkers(Boolean withMarkers) {
|
||||
this.withMarkers = withMarkers;
|
||||
}
|
||||
|
||||
public boolean getMarkersJson() {
|
||||
return this.markersJson;
|
||||
}
|
||||
|
||||
public void setMarkersJson(boolean markersJson) {
|
||||
this.markersJson = markersJson;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "withMarkers can only be supplied when FileReadingMode is 'lines'")
|
||||
public boolean isWithMarkersValid() {
|
||||
return this.withMarkers == null || FileReadingMode.lines == this.mode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.supplier.file;
|
||||
|
||||
/**
|
||||
* Defines the supported modes of reading and processing files.
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @author David Turanski
|
||||
*/
|
||||
public enum FileReadingMode {
|
||||
ref,
|
||||
lines,
|
||||
contents;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.supplier.file;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.integration.dsl.IntegrationFlowBuilder;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.dsl.FileInboundChannelAdapterSpec;
|
||||
import org.springframework.integration.file.dsl.Files;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({FileSupplierProperties.class, FileConsumerProperties.class})
|
||||
public class FileSupplierConfiguration {
|
||||
|
||||
private final FileSupplierProperties fileSupplierProperties;
|
||||
|
||||
private final FileConsumerProperties fileConsumerProperties;
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
@Qualifier("fileMessageSource")
|
||||
private FileReadingMessageSource fileMessageSource;
|
||||
|
||||
public FileSupplierConfiguration(FileSupplierProperties fileSupplierProperties,
|
||||
FileConsumerProperties fileConsumerProperties) {
|
||||
this.fileSupplierProperties = fileSupplierProperties;
|
||||
this.fileConsumerProperties = fileConsumerProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FileInboundChannelAdapterSpec fileMessageSource() {
|
||||
final FileInboundChannelAdapterSpec fileInboundChannelAdapterSpec =
|
||||
Files.inboundAdapter(this.fileSupplierProperties.getDirectory());
|
||||
if (StringUtils.hasText(this.fileSupplierProperties.getFilenamePattern())) {
|
||||
fileInboundChannelAdapterSpec.patternFilter(this.fileSupplierProperties.getFilenamePattern());
|
||||
}
|
||||
else if (this.fileSupplierProperties.getFilenameRegex() != null) {
|
||||
fileInboundChannelAdapterSpec.regexFilter(this.fileSupplierProperties.getFilenameRegex().pattern());
|
||||
}
|
||||
fileInboundChannelAdapterSpec.preventDuplicates(this.fileSupplierProperties.isPreventDuplicates());
|
||||
return fileInboundChannelAdapterSpec;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Flux<Message<?>> fileMessageFlux() {
|
||||
return Mono.<Message<?>>create(monoSink ->
|
||||
monoSink.onRequest(value ->
|
||||
monoSink.success(this.fileMessageSource.receive())))
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.repeatWhenEmpty(it -> it.delayElements(this.fileSupplierProperties.getDelayWhenEmpty()))
|
||||
.repeat();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression("environment['file.consumer.mode'] != 'ref'")
|
||||
public Publisher<Message<Object>> fileReadingFlow() {
|
||||
IntegrationFlowBuilder flowBuilder = IntegrationFlows.from(fileMessageFlux());
|
||||
return FileUtils.enhanceFlowForReadingMode(flowBuilder, this.fileConsumerProperties)
|
||||
.toReactivePublisher();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Supplier<Flux<Message<?>>> fileSupplier() {
|
||||
if (this.fileConsumerProperties.getMode() == FileReadingMode.ref) {
|
||||
return this::fileMessageFlux;
|
||||
}
|
||||
else {
|
||||
return () -> Flux.from(fileReadingFlow());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.supplier.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Properties for the file supplier.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@ConfigurationProperties("file.supplier")
|
||||
@Validated
|
||||
public class FileSupplierProperties {
|
||||
|
||||
private static final String DEFAULT_DIR = System.getProperty("java.io.tmpdir") +
|
||||
File.separator + "file-supplier";
|
||||
|
||||
/**
|
||||
* The directory to poll for new files.
|
||||
*/
|
||||
private File directory = new File(DEFAULT_DIR);
|
||||
|
||||
/**
|
||||
* Set to true to include an AcceptOnceFileListFilter which prevents duplicates.
|
||||
*/
|
||||
private boolean preventDuplicates = true;
|
||||
|
||||
/**
|
||||
* A simple ant pattern to match files.
|
||||
*/
|
||||
private String filenamePattern;
|
||||
|
||||
/**
|
||||
* A regex pattern to match files.
|
||||
*/
|
||||
private Pattern filenameRegex;
|
||||
|
||||
/**
|
||||
* Duration of delay when no new files are detected.
|
||||
*/
|
||||
private Duration delayWhenEmpty = Duration.ofSeconds(1);
|
||||
|
||||
public File getDirectory() {
|
||||
return this.directory;
|
||||
}
|
||||
|
||||
public void setDirectory(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public boolean isPreventDuplicates() {
|
||||
return this.preventDuplicates;
|
||||
}
|
||||
|
||||
public void setPreventDuplicates(boolean preventDuplicates) {
|
||||
this.preventDuplicates = preventDuplicates;
|
||||
}
|
||||
|
||||
public String getFilenamePattern() {
|
||||
return this.filenamePattern;
|
||||
}
|
||||
|
||||
public void setFilenamePattern(String filenamePattern) {
|
||||
this.filenamePattern = filenamePattern;
|
||||
}
|
||||
|
||||
public Pattern getFilenameRegex() {
|
||||
return this.filenameRegex;
|
||||
}
|
||||
|
||||
public void setFilenameRegex(Pattern filenameRegex) {
|
||||
this.filenameRegex = filenameRegex;
|
||||
}
|
||||
|
||||
//@AssertTrue(message = "filenamePattern and filenameRegex are mutually exclusive")
|
||||
|
||||
public boolean isExclusivePatterns() {
|
||||
return !(this.filenamePattern != null && this.filenameRegex != null);
|
||||
}
|
||||
|
||||
public Duration getDelayWhenEmpty() {
|
||||
return delayWhenEmpty;
|
||||
}
|
||||
|
||||
public void setDelayWhenEmpty(Duration delayWhenEmpty) {
|
||||
this.delayWhenEmpty = delayWhenEmpty;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.supplier.file;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.integration.dsl.IntegrationFlowBuilder;
|
||||
import org.springframework.integration.file.splitter.FileSplitter;
|
||||
import org.springframework.integration.file.transformer.FileToByteArrayTransformer;
|
||||
import org.springframework.integration.transformer.StreamTransformer;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @author Christian Tzolov
|
||||
*
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* Enhance an {@link IntegrationFlowBuilder} to add flow snippets, depending on
|
||||
* {@link FileConsumerProperties}.
|
||||
* @param flowBuilder the flow builder.
|
||||
* @param fileConsumerProperties the properties.
|
||||
* @return the updated flow builder.
|
||||
*/
|
||||
public static IntegrationFlowBuilder enhanceFlowForReadingMode(IntegrationFlowBuilder flowBuilder,
|
||||
FileConsumerProperties fileConsumerProperties) {
|
||||
switch (fileConsumerProperties.getMode()) {
|
||||
case contents:
|
||||
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
|
||||
MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE))
|
||||
.transform(new FileToByteArrayTransformer());
|
||||
break;
|
||||
case lines:
|
||||
Boolean withMarkers = fileConsumerProperties.getWithMarkers();
|
||||
if (withMarkers == null) {
|
||||
withMarkers = false;
|
||||
}
|
||||
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
|
||||
MimeTypeUtils.TEXT_PLAIN_VALUE))
|
||||
.split(new FileSplitter(true, withMarkers, fileConsumerProperties.getMarkersJson()));
|
||||
break;
|
||||
case ref:
|
||||
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
|
||||
MimeTypeUtils.APPLICATION_JSON_VALUE));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(fileConsumerProperties.getMode().name() +
|
||||
" is not a supported file reading mode.");
|
||||
}
|
||||
return flowBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance an {@link IntegrationFlowBuilder} to add flow snippets, depending on
|
||||
* {@link FileConsumerProperties}; used for streaming sources.
|
||||
* @param flowBuilder the flow builder.
|
||||
* @param fileConsumerProperties the properties.
|
||||
* @return the updated flow builder.
|
||||
*/
|
||||
public static IntegrationFlowBuilder enhanceStreamFlowForReadingMode(IntegrationFlowBuilder flowBuilder,
|
||||
FileConsumerProperties fileConsumerProperties) {
|
||||
switch (fileConsumerProperties.getMode()) {
|
||||
case contents:
|
||||
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
|
||||
MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE))
|
||||
.transform(new StreamTransformer());
|
||||
break;
|
||||
case lines:
|
||||
Boolean withMarkers = fileConsumerProperties.getWithMarkers();
|
||||
if (withMarkers == null) {
|
||||
withMarkers = false;
|
||||
}
|
||||
flowBuilder.enrichHeaders(Collections.<String, Object>singletonMap(MessageHeaders.CONTENT_TYPE,
|
||||
MimeTypeUtils.TEXT_PLAIN_VALUE))
|
||||
.split(new FileSplitter(true, withMarkers, fileConsumerProperties.getMarkersJson()));
|
||||
break;
|
||||
case ref:
|
||||
default:
|
||||
throw new IllegalArgumentException(fileConsumerProperties.getMode().name() +
|
||||
" is not a supported file reading mode when streaming.");
|
||||
}
|
||||
return flowBuilder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2020 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.cloud.fn.supplier.file;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@SpringBootTest
|
||||
@DirtiesContext
|
||||
public class AbstractFileSupplierTests {
|
||||
|
||||
@TempDir
|
||||
static Path tempDir;
|
||||
|
||||
@Autowired
|
||||
Supplier<Flux<Message<?>>> fileSupplier;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() {
|
||||
System.setProperty("file.supplier.directory", tempDir.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterAll() {
|
||||
System.clearProperty("file.supplier.directory");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user