Skip to content

Commit a97fd89

Browse files
committed
Add event repeat logic
DEVSIX-5973
1 parent d5ce39e commit a97fd89

File tree

3 files changed

+160
-28
lines changed

3 files changed

+160
-28
lines changed

commons/src/main/java/com/itextpdf/commons/actions/ProductEventHandler.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.commons.actions.confirmations.ConfirmEvent;
2626
import com.itextpdf.commons.actions.confirmations.ConfirmedEventWrapper;
2727
import com.itextpdf.commons.actions.contexts.UnknownContext;
28-
import com.itextpdf.commons.exceptions.UnknownProductException;
29-
import com.itextpdf.commons.logs.CommonsLogMessageConstant;
3028
import com.itextpdf.commons.actions.processors.DefaultITextProductEventProcessor;
3129
import com.itextpdf.commons.actions.processors.ITextProductEventProcessor;
3230
import com.itextpdf.commons.actions.sequence.SequenceId;
31+
import com.itextpdf.commons.exceptions.ProductEventHandlerRepeatException;
32+
import com.itextpdf.commons.exceptions.UnknownProductException;
33+
import com.itextpdf.commons.logs.CommonsLogMessageConstant;
3334
import com.itextpdf.commons.utils.MessageFormatUtil;
3435

3536
import java.util.ArrayList;
@@ -47,7 +48,11 @@ This file is part of the iText (R) project.
4748
*/
4849
final class ProductEventHandler extends AbstractContextBasedEventHandler {
4950
static final ProductEventHandler INSTANCE = new ProductEventHandler();
51+
5052
private static final Logger LOGGER = LoggerFactory.getLogger(ProductEventHandler.class);
53+
// The constant has the following value for two reasons. First, to avoid the infinite loop.
54+
// Second, to retry event processing several times for technical reasons.
55+
private static final int MAX_EVENT_RETRY_COUNT = 4;
5156

5257
private final ConcurrentHashMap<String, ITextProductEventProcessor> processors = new ConcurrentHashMap<>();
5358
private final WeakHashMap<SequenceId, List<AbstractProductProcessITextEvent>> events = new WeakHashMap<>();
@@ -63,24 +68,17 @@ private ProductEventHandler() {
6368
*/
6469
@Override
6570
protected void onAcceptedEvent(AbstractContextBasedITextEvent event) {
66-
if (! (event instanceof AbstractProductProcessITextEvent)) {
67-
return;
68-
}
69-
final AbstractProductProcessITextEvent productEvent = (AbstractProductProcessITextEvent) event;
70-
final String productName = productEvent.getProductName();
71-
final ITextProductEventProcessor productEventProcessor = getActiveProcessor(productName);
72-
if (productEventProcessor == null) {
73-
throw new UnknownProductException(
74-
MessageFormatUtil.format(UnknownProductException.UNKNOWN_PRODUCT, productName));
75-
}
76-
productEventProcessor.onEvent(productEvent);
77-
if (productEvent.getSequenceId() != null) {
78-
if (productEvent instanceof ConfirmEvent) {
79-
wrapConfirmedEvent((ConfirmEvent) productEvent, productEventProcessor);
80-
} else {
81-
addEvent(productEvent.getSequenceId(), productEvent);
71+
for (int i = 0; i < MAX_EVENT_RETRY_COUNT; i++) {
72+
try {
73+
tryProcessEvent(event);
74+
// process succeeded
75+
return;
76+
} catch (ProductEventHandlerRepeatException repeatException) {
77+
// ignore this exception to retry the processing
8278
}
8379
}
80+
// the final processing retry
81+
tryProcessEvent(event);
8482
}
8583

8684
ITextProductEventProcessor addProcessor(ITextProductEventProcessor processor) {
@@ -138,6 +136,29 @@ void addEvent(SequenceId id, AbstractProductProcessITextEvent event) {
138136
}
139137
}
140138

139+
private void tryProcessEvent(AbstractContextBasedITextEvent event) {
140+
if (! (event instanceof AbstractProductProcessITextEvent)) {
141+
return;
142+
}
143+
final AbstractProductProcessITextEvent productEvent = (AbstractProductProcessITextEvent) event;
144+
final String productName = productEvent.getProductName();
145+
final ITextProductEventProcessor productEventProcessor = getActiveProcessor(productName);
146+
if (productEventProcessor == null) {
147+
throw new UnknownProductException(
148+
MessageFormatUtil.format(UnknownProductException.UNKNOWN_PRODUCT, productName));
149+
}
150+
151+
productEventProcessor.onEvent(productEvent);
152+
153+
if (productEvent.getSequenceId() != null) {
154+
if (productEvent instanceof ConfirmEvent) {
155+
wrapConfirmedEvent((ConfirmEvent) productEvent, productEventProcessor);
156+
} else {
157+
addEvent(productEvent.getSequenceId(), productEvent);
158+
}
159+
}
160+
}
161+
141162
private void wrapConfirmedEvent(ConfirmEvent event, ITextProductEventProcessor productEventProcessor) {
142163
synchronized (events) {
143164
final List<AbstractProductProcessITextEvent> eventsList = events.get(event.getSequenceId());
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2021 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.commons.exceptions;
24+
25+
/**
26+
* The class represents a signal to the event handler that it is necessary to repeat the handling of the current event.
27+
*/
28+
public final class ProductEventHandlerRepeatException extends ITextException {
29+
/**
30+
* Creates a new instance of {@link ProductEventHandlerRepeatException} based on message.
31+
*
32+
* @param message the detail message
33+
*/
34+
public ProductEventHandlerRepeatException(String message) {
35+
super(message);
36+
}
37+
}

commons/src/test/java/com/itextpdf/commons/actions/ProductEventHandlerTest.java

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,37 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.commons.actions.confirmations.ConfirmEvent;
2626
import com.itextpdf.commons.actions.confirmations.ConfirmedEventWrapper;
27+
import com.itextpdf.commons.actions.processors.ITextProductEventProcessor;
2728
import com.itextpdf.commons.actions.sequence.SequenceId;
2829
import com.itextpdf.commons.ecosystem.ITextTestEvent;
30+
import com.itextpdf.commons.exceptions.ProductEventHandlerRepeatException;
2931
import com.itextpdf.commons.exceptions.UnknownProductException;
3032
import com.itextpdf.commons.utils.MessageFormatUtil;
33+
import com.itextpdf.test.AssertUtil;
3134
import com.itextpdf.test.ExtendedITextTest;
3235
import com.itextpdf.test.annotations.type.UnitTest;
3336

3437
import org.junit.Assert;
35-
import org.junit.Rule;
38+
import org.junit.Before;
3639
import org.junit.Test;
3740
import org.junit.experimental.categories.Category;
38-
import org.junit.rules.ExpectedException;
3941

4042
@Category(UnitTest.class)
4143
public class ProductEventHandlerTest extends ExtendedITextTest {
42-
@Rule
43-
public ExpectedException junitExpectedException = ExpectedException.none();
44+
@Before
45+
public void clearProcessors() {
46+
ProductEventHandler.INSTANCE.clearProcessors();
47+
}
4448

4549
@Test
4650
public void unknownProductTest() {
4751
ProductEventHandler handler = ProductEventHandler.INSTANCE;
4852

49-
junitExpectedException.expect(UnknownProductException.class);
50-
junitExpectedException.expectMessage(
51-
MessageFormatUtil.format(UnknownProductException.UNKNOWN_PRODUCT, "Unknown Product"));
52-
53-
handler.onAcceptedEvent(new ITextTestEvent(new SequenceId(), null, "test-event",
54-
"Unknown Product"));
53+
AbstractContextBasedITextEvent event = new ITextTestEvent(new SequenceId(), null, "test-event", "Unknown Product");
54+
Exception ex = Assert.assertThrows(UnknownProductException.class,
55+
() -> handler.onAcceptedEvent(event));
56+
Assert.assertEquals(MessageFormatUtil.format(UnknownProductException.UNKNOWN_PRODUCT, "Unknown Product"),
57+
ex.getMessage());
5558
}
5659

5760
@Test
@@ -114,4 +117,75 @@ public void confirmEventTest() {
114117
Assert.assertTrue(handler.getEvents(sequenceId).get(0) instanceof ConfirmedEventWrapper);
115118
Assert.assertEquals(event, ((ConfirmedEventWrapper) handler.getEvents(sequenceId).get(0)).getEvent());
116119
}
120+
121+
@Test
122+
public void repeatEventHandlingWithFiveExceptionOnProcessingTest() {
123+
ProductEventHandler handler = ProductEventHandler.INSTANCE;
124+
125+
handler.addProcessor(new RepeatEventProcessor(5));
126+
127+
AbstractContextBasedITextEvent event = new ITextTestEvent(new SequenceId(), null, "test",
128+
ProductNameConstant.ITEXT_CORE);
129+
130+
Exception e = Assert.assertThrows(ProductEventHandlerRepeatException.class,
131+
() -> handler.onAcceptedEvent(event));
132+
Assert.assertEquals("customMessage5", e.getMessage());
133+
}
134+
135+
@Test
136+
public void repeatEventHandlingWithFourExceptionOnProcessingTest() {
137+
ProductEventHandler handler = ProductEventHandler.INSTANCE;
138+
139+
handler.addProcessor(new RepeatEventProcessor(4));
140+
141+
AbstractContextBasedITextEvent event = new ITextTestEvent(new SequenceId(), null, "test",
142+
ProductNameConstant.ITEXT_CORE);
143+
144+
AssertUtil.doesNotThrow(() -> handler.onAcceptedEvent(event));
145+
}
146+
147+
@Test
148+
public void repeatEventHandlingWithOneExceptionOnProcessingTest() {
149+
ProductEventHandler handler = ProductEventHandler.INSTANCE;
150+
151+
handler.addProcessor(new RepeatEventProcessor(1));
152+
153+
AbstractContextBasedITextEvent event = new ITextTestEvent(new SequenceId(), null, "test",
154+
ProductNameConstant.ITEXT_CORE);
155+
156+
AssertUtil.doesNotThrow(() -> handler.onAcceptedEvent(event));
157+
}
158+
159+
private static class RepeatEventProcessor implements ITextProductEventProcessor {
160+
private final int exceptionsCount;
161+
private int exceptionCounter = 0;
162+
163+
public RepeatEventProcessor(int exceptionsCount) {
164+
this.exceptionsCount = exceptionsCount;
165+
}
166+
167+
@Override
168+
public void onEvent(AbstractProductProcessITextEvent event) {
169+
if (exceptionCounter < exceptionsCount) {
170+
exceptionCounter++;
171+
throw new ProductEventHandlerRepeatException("customMessage" + exceptionCounter);
172+
}
173+
174+
}
175+
176+
@Override
177+
public String getProductName() {
178+
return ProductNameConstant.ITEXT_CORE;
179+
}
180+
181+
@Override
182+
public String getUsageType() {
183+
return "someUsage";
184+
}
185+
186+
@Override
187+
public String getProducer() {
188+
return "someProducer";
189+
}
190+
}
117191
}

0 commit comments

Comments
 (0)