Duplicate message processing using Spring Cloud Stream with Kafka - duplicates

I am using Spring Cloud Stream with Kafka binder. It works great but the client receives duplicate messages. Already tried all of Kafka Consumer Properties with no result.
Check 2 classes in my example of application - AggregateApplication and EventFilterApplication. In case I run EventFilterApplication - have only 1 message, in case for AggregateApplication - 2 same messages.
Here is my code below:
1) Aggregator
import com.example.EventFilterApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.aggregate.AggregateApplicationBuilder;
#SpringBootApplication
public class AggregateApplication {
public static void main(String[] args) {
new AggregateApplicationBuilder(new Object[]{EventFilterApplication.class}, args)
.from(EventFilterApplication.class)
.run(args);
}
}
2) EventFilterApplication
#SpringBootApplication
#EnableBinding(EventFilterApplication.LiveProcessor.class)
public class EventFilterApplication {
#Autowired
LiveProcessor source;
#StreamListener(LiveProcessor.INPUT)
public void handle(byte[] event) {
try {
System.out.println(new Date().getTime() + ": event was processed:" + Arrays.toString(event));
} catch (Exception e) {
System.out.println(String.format("Error={%s} on processing message=%s", e.getMessage(), Arrays.toString(event)));
}
}
public static void main(String[] args) {
SpringApplication.run(EventFilterApplication.class, args);
}
interface LiveProcessor extends Source {
String INPUT = "liveSource";
#Input(INPUT)
SubscribableChannel input();
}
}
3) application.yml
spring:
cloud:
stream:
kafka:
binder:
brokers: kafka-broker.example.com:9092
defaultBrokerPort: 9092
defaultZkPort: 2181
zkNodes: kafka-zookeeper.example.com
type: kafka
bindings:
liveSource:
binder: kafka
consumer:
headerMode: raw
autoCommitOffset: true
destination: topic_example_name
4) build.gradle
buildscript {
ext { springBootVersion = '1.4.2.RELEASE' }
repositories {
jcenter()
maven { url 'http://repo.spring.io/plugins-release' }
}
dependencies {
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7")
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
classpath("io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE")
}
}
ext['logstashLogbackEncoderV'] = '4.8'
ext['springCloudV'] = 'Camden.SR1'
ext['springCloudStreamV'] = 'Brooklyn.SR2'
ext['springIntegrationKafkaV'] = '1.3.1.RELEASE'
subprojects {
apply plugin: 'java'
apply plugin: 'propdeps'
apply plugin: 'propdeps-idea'
apply plugin: "io.spring.dependency-management"
sourceCompatibility = 1.8
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.SR1"
mavenBom "org.springframework.cloud:spring-cloud-stream-dependencies:Brooklyn.SR2"
mavenBom "org.springframework.cloud.stream.app:spring-cloud-stream-app-dependencies:1.0.4.RELEASE"
}
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") {
exclude module: "spring-boot-starter-tomcat"
exclude group: 'log4j'
}
compile("org.springframework.cloud:spring-cloud-starter-stream-kafka")
compile("org.springframework.integration:spring-integration-kafka:$springIntegrationKafkaV") {
exclude group: "org.slf4j"
}
compile("org.springframework.cloud:spring-cloud-stream:")
compile("org.springframework.cloud:spring-cloud-starter-sleuth")
compile("net.logstash.logback:logstash-logback-encoder:${logstashLogbackEncoderV}")
testCompile("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
exclude group: "org.slf4j"
}
}
}

The duplication is caused by EventFilterApplication as the parent root:
public class AggregateApplication {
public static void main(String[] args) {
new AggregateApplicationBuilder(new Object[]{EventFilterApplication.class}, args)
.from(EventFilterApplication.class)
.run(args);
}
}
That is very likely to create two subscriptions. Instead of adding EventFilterApplication as root, you can simply do:
public class AggregateApplication {
public static void main(String[] args) {
new AggregateApplicationBuilder(args)
.from(EventFilterApplication.class)
// rest of the pipeline
.run(args);
}
}
If you don't need to create an aggregate, this should be enough:
public static void main(String[] args) {
SpringApplication.run(EventFilterApplication.class, args);
}
EDIT: added one extra example and clarified answer.

Related

Spring REST docs: How to migrate Rule to JUnit 5

I migrated my Spring tests to JUnit 5, and they work fine. However, I don't know how to migrate #Rule public JUnitRestDocumentation restDocumentation = ....
Any hint is appreciated.
Spring RestDocs 2 introduces a new class : RestDocumentationExtension for JUnit 5. You can use it instead of Rule
#ExtendWith(RestDocumentationExtension.class)
public class JUnit5ExampleTests {
A complete example is available on https://github.com/spring-projects/spring-restdocs/blob/v2.0.0.RELEASE/samples/junit5/src/test/java/com/example/junit5/SampleJUnit5ApplicationTests.java
The extension is documented on https://github.com/spring-projects/spring-restdocs/blob/master/docs/src/docs/asciidoc/getting-started.adoc#setting-up-your-junit-5-tests
Spring RestDocs 2 requires Spring 5 and JDK 8
Until the issue is officially resolved, I was able to get it working with a JUnit 5 extension (below).
Using that extension, I modified my test class thusly:
#ExtendWith(RestDocsExtension.class)
and
#BeforeEach
void setUp(WebApplicationContext wac, ManualRestDocumentation restDocumentation) throws Exception {
Here is the Extension.
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ContainerExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestExtensionContext;
import org.springframework.restdocs.ManualRestDocumentation;
import java.lang.reflect.Method;
import java.util.Optional;
public class RestDocsExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver {
private static final String REST_DOC_STORE_KEY = "restDocumentation";
private ManualRestDocumentation restDocumentation;
#Override
public void beforeAll(ContainerExtensionContext context) throws Exception {
if (restDocumentation == null) {
restDocumentation = new ManualRestDocumentation("target/generated-snippets");
getStore(context).put(REST_DOC_STORE_KEY, restDocumentation);
}
}
#Override
public void beforeEach(TestExtensionContext context) throws Exception {
Optional<Class<?>> testClass = context.getTestClass();
Optional<Method> methodNameOpt = context.getTestMethod();
if (testClass.isPresent() && methodNameOpt.isPresent()) {
getDoc(context).beforeTest(testClass.get().getClass(), methodNameOpt.get().getName());
} else {
throw new Exception("TestExtensionContext with no class or method. wat");
}
}
#Override
public void afterEach(TestExtensionContext context) throws Exception {
getDoc(context).afterTest();
}
#Override
public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == ManualRestDocumentation.class;
}
#Override
public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getDoc(extensionContext);
}
private ManualRestDocumentation getDoc(ExtensionContext context) {
return (ManualRestDocumentation) getStore(context).get(REST_DOC_STORE_KEY);
}
private ExtensionContext.Store getStore(ExtensionContext context) {
return context.getStore(ExtensionContext.Namespace.DEFAULT);
}
}

How can I set a pattern in a DBAppender?

I using logback and put a pettern into a dbappender, but it doesn´t work.
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource
class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>net.sourceforge.jtds.jdbc.Driver</driverClass>
<url>jdbc:jtds:sqlserver://xxx.xxx.xxx.xx:1433/granica</url>
<user>java</user>
<password>java</password>
</connectionSource>
<encoder>
<pattern>%d{HH:mm:ss.SSS} - %msg%n</pattern>
</encoder>
</appender>
someone know how to fix that?
thanks in advance!
You can't put a pattern in a DBAppender:
"The DBAppender inserts logging events into three database tables in a format independent of the Java programming language.
*These three tables are logging_event, logging_event_property and logging_event_exception. They must exist before DBAppender can be used. Logback ships with SQL scripts that will create the tables."*
Alternative solution: If you realy need this pattern, then you should create an extra dataBbase table/view/sql that generaties that output for you, based on the existing tables. This can be done with a simple SQL, View or Triggers. Use standard SQL to create the output you need.
Good luck!
I managed to customize the formatted message easily enough by defining a couple of custom classes, one extending the DBAppender and the other implementing the ILoggingEvent interface:
package com.mypackage;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Map;
import org.slf4j.Marker;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.db.DBAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggerContextVO;
/**
*
* #author PJ_Finnegan
*
* To override the default DBAppender with a custom FORMATTED_MESSAGE
*
*/
public class MdcDbAppender extends DBAppender {
#Override
protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement)
throws Throwable {
// use a special event with custom formatted message
MdcLoggingEvent customEvent = new MdcLoggingEvent(event);
super.subAppend(customEvent, connection, insertStatement);
}
}
/**
*
* #author PJ_Finnegan
*
* To override the getFormattedMessage() method prepending MDC info
*
*/
class MdcLoggingEvent implements ILoggingEvent {
private ILoggingEvent mOrigEvent;
public MdcLoggingEvent(ILoggingEvent origEvent) {
mOrigEvent = origEvent;
}
#Override
public String getThreadName() {
return mOrigEvent.getThreadName();
}
#Override
public Level getLevel() {
return mOrigEvent.getLevel();
}
#Override
public String getMessage() {
return mOrigEvent.getMessage();
}
#Override
public Object[] getArgumentArray() {
return mOrigEvent.getArgumentArray();
}
#Override
public String getFormattedMessage() {
// add my MDC info as a prefix
String mdcVals[] = new String[] { "NA", "NA", "NA" };
Map<String, String> mpm = mOrigEvent.getMDCPropertyMap();
if (mpm != null) {
int i = 0;
for (String key : new String[] { "jobName", "jobInstanceId", "jobExecutionId" }) {
if (mpm.containsKey(key)) {
mdcVals[i] = mpm.get(key);
}
i++;
}
}
return String.format("[%s-%s-%s] ", mdcVals[0], mdcVals[1], mdcVals[2]) + mOrigEvent.getFormattedMessage();
}
#Override
public String getLoggerName() {
return mOrigEvent.getLoggerName();
}
#Override
public LoggerContextVO getLoggerContextVO() {
return mOrigEvent.getLoggerContextVO();
}
#Override
public IThrowableProxy getThrowableProxy() {
return mOrigEvent.getThrowableProxy();
}
#Override
public StackTraceElement[] getCallerData() {
return mOrigEvent.getCallerData();
}
#Override
public boolean hasCallerData() {
return mOrigEvent.hasCallerData();
}
#Override
public Marker getMarker() {
return mOrigEvent.getMarker();
}
#Override
public Map<String, String> getMDCPropertyMap() {
return mOrigEvent.getMDCPropertyMap();
}
#Override
public Map<String, String> getMdc() {
return mOrigEvent.getMdc();
}
#Override
public long getTimeStamp() {
return mOrigEvent.getTimeStamp();
}
#Override
public void prepareForDeferredProcessing() {
mOrigEvent.prepareForDeferredProcessing();
}
}
This way the LOGGING_EVENT.FORMATTED_MESSAGE column values on the DB will have the string [jobName-jobInstanceId-jobExecutionId] (whose value I had previously put in MDC) prepended:
TIMESTMP
FORMATTED_MESSAGE
1635255763784
[test-2760-3980] HikariPool-1 - Shutdown completed.
Of course you want to use your custom class in your logback.xml file instead of the stock DBAppender:
<!-- DB appender -->
<!-- use our custom class to override the FORMATTED_MESSAGE default pattern -->
<!-- <appender name="ORADB" class="ch.qos.logback.classic.db.DBAppender"> -->
<appender name="ORADB" class="com.mypackage.MdcDbAppender">
<connectionSource
...
Nothing prevents from overriding the ILoggingEvent.getFormattedMessage() method with more elaborated manipulations.

Openejb rest integration tests with exception mappers

I'm writing some integration tests towards my jax-rs service where I have a set of exception mappers. So, when performing a given request I expect a certain response code based on the exception mapper. The problem is that I cannot get the exception mappers to be invoked when running in this environment.
My service which should throw a logicalexception in my test:
#Stateless
#Path("/baseCustomer")
public class BaseCustomerService {
#EJB //this one gets mocked in the unittest
private BaseCustomerManagerBean customerManager;
#POST
#Path("crud")
#Consumes({MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML})
public Hkunde createCustomer(Hkunde newCustomer) throws LogicalException {
//throws exception according to mocking
return customerManager.createCustomer(newCustomer);
}
And the exception mapper:
#Provider
public class LogicalExceptionMapper implements ExceptionMapper<LogicalException> {
#Override
public Response toResponse(LogicalException exception) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
I set up my tests like this:
#Mock
private BaseCustomerManagerBean baseCustomerManager;
private HttpClient httpClient;
private BaseCustomerServiceClient client;
#Configuration
public Properties config() throws Exception {
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory");
properties.setProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, Boolean.TRUE.toString());
properties.setProperty(DeploymentFilterable.CLASSPATH_INCLUDE, LogicalExceptionMapper.class.getName());
properties.setProperty("openejb.jaxrs.providers.auto", "true");
properties.setProperty("openejb.servicemanager.enabled", "true");
return properties;
}
#MockInjector
public Class<?> mockitoInjector() {
return MockitoInjector.class;
}
#Module
public EjbModule createModule() throws Exception {
final StatelessBean bean = (StatelessBean) new StatelessBean(BaseCustomerService.class).localBean();
bean.setRestService(true);
final EjbJar ejbJar = new EjbJar();
ejbJar.addEnterpriseBean(bean);
final OpenejbJar openejbJar = new OpenejbJar();
openejbJar.addEjbDeployment(new EjbDeployment(ejbJar.getEnterpriseBeans()[0]));
EjbModule module = new EjbModule(ejbJar);
module.setOpenejbJar(openejbJar);
return module;
}
#Module
public Class[] exceptionMappers() {
return new Class[]{LogicalExceptionMapper.class};
}
#Before
public void setup() {
ServiceHost serviceHost = new ServiceHost("http://localhost:4204/BaseCustomerServiceTest");
httpClient = new HttpClient(serviceHost);
client = new BaseCustomerServiceClient(httpClient);
}
#Test
public void createCustomer_givenLogicalException_expectsLogicalException() throws LogicalException {
Hkunde expected = new Hkunde(true);
when(baseCustomerManager.createCustomer(expected)).thenThrow(new LogicalException("mock"));
try {
client.createCustomer(expected);
fail("Expected LogicalException");
} catch (LogicalException ex) {
}
verify(baseCustomerManager).createCustomer(expected);
}
So when I execute the test, my client will read the response code from the response and throw an exception based on this code.
The problem is that the exception mapper is never invoked, and I always receive a 500 internal server error, instead of the "forbidden" response. I'm guessing I need to add some more info when setting up the ejbjar or something like that.
Thanks!
This example http://svn.apache.org/repos/asf/openejb/trunk/openejb/examples/rest-applicationcomposer/src/test/java/org/superbiz/composed/rest/GreetingServiceTest.java (via http://rmannibucau.wordpress.com/2012/09/13/use-mockito-with-openejb/ ;-)) shows exactly what you want.
Add the following after openejbJar.addEjbDeployment(... and it should work.
final Properties properties = openejbJar.getEjbDeployment().iterator().next().getProperties();
properties.setProperty("cxf.jaxrs.providers", LogicalExceptionMapper.class.getName());
Here is a minimal working example (using openejb-cxf-rs 4.5.0 and openejb-core 4.5.0):
import java.util.Properties;
import javax.ejb.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.openejb.OpenEjbContainer;
import org.apache.openejb.config.EjbModule;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.StatelessBean;
import org.apache.openejb.jee.oejb3.EjbDeployment;
import org.apache.openejb.jee.oejb3.OpenejbJar;
import org.apache.openejb.junit.ApplicationComposer;
import org.apache.openejb.junit.Configuration;
import org.apache.openejb.junit.Module;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(ApplicationComposer.class)
public class RestWithExceptionMapper {
#Configuration
public Properties configuration() {
return new Properties() {
{
setProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, Boolean.TRUE.toString());
}
};
}
#Module
public EjbModule app() {
final StatelessBean bean = (StatelessBean) new StatelessBean(MyResource.class).localBean();
bean.setRestService(true);
final EjbJar ejbJar = new EjbJar();
ejbJar.addEnterpriseBean(bean);
final OpenejbJar openejbJar = new OpenejbJar();
openejbJar.addEjbDeployment(new EjbDeployment(ejbJar.getEnterpriseBeans()[0]));
final Properties properties = openejbJar.getEjbDeployment().iterator().next().getProperties();
properties.setProperty("cxf.jaxrs.providers", MyExceptionMapper.class.getName());
final EjbModule module = new EjbModule(ejbJar);
module.setOpenejbJar(openejbJar);
return module;
}
public static class FooException extends RuntimeException {
}
public static class MyExceptionMapper implements ExceptionMapper<FooException> {
#Override
public Response toResponse(final FooException t) {
return Response.ok("Objection!").build();
}
}
#Path(value = "/test")
public static class MyResource {
#GET
#Path(value = "/throw")
public String throwException() {
throw new FooException();
}
}
#Test
public void checkServiceWasDeployed() {
assertEquals("Objection!", WebClient.create("http://localhost:4204/RestWithExceptionMapper").path("/test/throw").get(String.class));
}
}

documentFilter.insert never called

I'm trying to set a documentFilter for my JTextArea. Having overriden the insert(...) method I admitted that it is never called. What's wrong? A piece of code:
package jaba;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
public class Main extends JFrame {
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(640, 480);
setLayout(new FlowLayout());
add(txt);
Document doc = txt.getDocument();
if (doc instanceof AbstractDocument) {
((AbstractDocument)doc).setDocumentFilter(new DocumentFilter() {
#Override
public void insertString(DocumentFilter.FilterBypass fb,
int offset, String string, AttributeSet att)
throws BadLocationException {
if (string.toLowerCase().contains("ass")) {
super.insertString(fb, offset, "###", att);
} else {
super.insertString(fb, offset, string, att);
}
}
});
} else {
txt.setText("error setting filter");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Main().setVisible(true);
}
});
}
private JTextArea txt = new JTextArea(40, 40);
}
Having overriden the insert(...) method I admitted that it is never called.
Changes to the text in Swing components ultimately invoke the replace(...) method of the DocumentFilter.
The insertString(...) method is only invoked when you update the Document directly by using code like:
textField.getDocument().insertString(...);
So you need to make sure that you also override the replace() method in the DocumentFilter.

Selenium 2 and JUnit4: How to capture screenshot on exception?

I want to capture a screenshot only upon unexpected exception.
Note.- This answer could be outdated. The answer is based on Selenium 2.15
Using TestWatcher does the trick (the unit test must extend following BaseTest):
public abstract class BaseTest {
// ...
protected WebDriver driver;
#Rule
public TestRule testWatcher = new TestWatcher() {
#Override
public void starting(Description desc) {
LOG.info("Launching browser...");
driver = Utils.getFirefoxDriver();
}
#Override
public void finished(Description desc) {
LOG.info("Quitting driver...");
driver.quit();
}
#Override
public void failed(Throwable e, Description d) {
LOG.debug("Creating screenshot...");
File scrFile = ((TakesScreenshot) driver).getScreenshotAs(
OutputType.FILE);
String scrFilename = "Screenshot.png";
File outputFile = new File(SCREEN_SHOTS_RESULTS_PATH, scrFilename);
LOG.info(scrFilename + " screenshot created.");
try {
org.​apache.​commons.​io.FileUtils.copyFile(scrFile, outputFile);
} catch (IOException ioe) {
LOG.error("Error copying screenshot after exception.", ioe);
}
}
};
}
Note
Utils.getFirefoxDriver() returns a customized WebDriver. Something like:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
public class Utils {
// ...
public static WebDriver getFirefoxDriver() {
FirefoxProfile firefoxProfile = new FirefoxProfile();
// Profile customization. For example:
// firefoxProfile.addExtension("firebug-1.8.4-fx.xpi");
// firefoxProfile.setPreference("extensions.firebug.currentVersion","1.8.4");
FirefoxBinary firefox = new FirefoxBinary();
// Firefox customization. For example:
// firefox.setEnvironmentProperty("DISPLAY", display);
WebDriver driver = new FirefoxDriver(firefox, firefoxProfile);
// WebDriver customizations. For example:
// driver.manage().timeouts().implicitlyWait(SHORT_TIMEOUT_S, TimeUnit.SECONDS);
return driver;
}
}