Skip to content

Commit 33c3b6e

Browse files
committed
DataHandlerCounter disabling hooks mechanism
DEVSIX-5183
1 parent c029424 commit 33c3b6e

File tree

5 files changed

+312
-28
lines changed

5 files changed

+312
-28
lines changed

io/src/main/java/com/itextpdf/io/LogMessageConstant.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,12 @@ public final class LogMessageConstant {
205205
public static final String TYPE3_FONT_INITIALIZATION_ISSUE = "Type 3 font issue. Font cannot be initialized correctly.";
206206
public static final String TYPOGRAPHY_NOT_FOUND = "Cannot find pdfCalligraph module, which was implicitly " +
207207
"required by one of the layout properties";
208-
public static final String UNABLE_TO_INVERT_GRADIENT_TRANSFORMATION = "Unable to invert gradient transformation, ignoring it";
209208
public static final String UNABLE_TO_APPLY_PAGE_DEPENDENT_PROP_UNKNOWN_PAGE_ON_WHICH_ELEMENT_IS_DRAWN = "Unable to apply page dependent property, because the page on which element is drawn is unknown. Usually this means that element was added to the Canvas instance that was created not with constructor taking PdfPage as argument. Not processed property: {0}";
209+
public static final String UNABLE_TO_INTERRUPT_THREAD = "Unable to interrupt a thread";
210+
public static final String UNABLE_TO_INVERT_GRADIENT_TRANSFORMATION = "Unable to invert gradient transformation, ignoring it";
210211
public static final String UNABLE_TO_REGISTER_EVENT_DATA_HANDLER_SHUTDOWN_HOOK = "Unable to register event data handler shutdown hook because of security reasons.";
211212
public static final String UNABLE_TO_SEARCH_FOR_EVENT_CONTEXT = "It is impossible to retrieve event context because of the security reasons. Event counting may behave in unexpected way";
213+
public static final String UNABLE_TO_UNREGISTER_EVENT_DATA_HANDLER_SHUTDOWN_HOOK = "Unable to unregister event data handler shutdown hook because of security permissions";
212214
public static final String UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING = "Unexpected behaviour during table row collapsing. Calculated rowspan was less then 1.";
213215
public static final String UNEXPECTED_EVENT_HANDLER_SERVICE_THREAD_EXCEPTION = "Unexpected exception encountered in service thread. Shutting it down.";
214216
public static final String UNKNOWN_CMAP = "Unknown CMap {0}";

kernel/src/main/java/com/itextpdf/kernel/PdfException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public class PdfException extends RuntimeException {
136136
public static final String CodabarMustHaveOneAbcdAsStartStopCharacter = "Codabar must have one of 'ABCD' as start/stop character.";
137137
public static final String ColorSpaceNotFound = "ColorSpace not found.";
138138
public static final String ContentStreamMustNotInvokeOperatorsThatSpecifyColorsOrOtherColorRelatedParameters = "Content stream must not invoke operators that specify colors or other color related parameters in the graphics state.";
139+
public static final String DataHandlerCounterHasBeenDisabled = "Data handler counter has been disabled";
139140
public static final String DecodeParameterType1IsNotSupported = "Decode parameter type {0} is not supported.";
140141
public static final String DefaultAppearanceNotFound = "DefaultAppearance is required but not found";
141142
public static final String DefaultcryptfilterNotFoundEncryption = "/DefaultCryptFilter not found (encryption).";

kernel/src/main/java/com/itextpdf/kernel/counter/DataHandlerCounter.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.kernel.counter;
4545

46+
import com.itextpdf.kernel.PdfException;
4647
import com.itextpdf.kernel.counter.context.IContext;
4748
import com.itextpdf.kernel.counter.context.UnknownContext;
4849
import com.itextpdf.kernel.counter.data.EventDataHandler;
@@ -51,30 +52,74 @@ This file is part of the iText (R) project.
5152
import com.itextpdf.kernel.counter.event.IEvent;
5253
import com.itextpdf.kernel.counter.event.IMetaInfo;
5354

55+
import java.io.Closeable;
56+
5457
/**
5558
* Counter based on {@link EventDataHandler}.
56-
* Registers shutdown hook and thread for triggering event processing after wait time
59+
* Registers shutdown hook and thread for triggering event processing after wait time.
5760
*
5861
* @param <T> The data signature class
5962
* @param <V> The event data class
6063
*/
61-
public class DataHandlerCounter<T, V extends EventData<T>> extends EventCounter {
64+
public class DataHandlerCounter<T, V extends EventData<T>> extends EventCounter implements Closeable {
65+
66+
private volatile boolean closed = false;
6267

6368
private final EventDataHandler<T, V> dataHandler;
6469

70+
/**
71+
* Create an instance with provided data handler and {@link UnknownContext#PERMISSIVE}
72+
* fallback context.
73+
*
74+
* @param dataHandler the {@link EventDataHandler} for events handling
75+
*/
6576
public DataHandlerCounter(EventDataHandler<T, V> dataHandler) {
6677
this(dataHandler, UnknownContext.PERMISSIVE);
6778
}
6879

80+
/**
81+
* Create an instance with provided data handler and fallback context.
82+
*
83+
* @param dataHandler the {@link EventDataHandler} for events handling
84+
* @param fallback the fallback {@link IContext context}
85+
*/
6986
public DataHandlerCounter(EventDataHandler<T, V> dataHandler, IContext fallback) {
7087
super(fallback);
7188
this.dataHandler = dataHandler;
72-
EventDataHandlerUtil.<T, V>registerProcessAllShutdownHook(dataHandler);
73-
EventDataHandlerUtil.<T, V>registerTimedProcessing(dataHandler);
89+
EventDataHandlerUtil.<T, V>registerProcessAllShutdownHook(this.dataHandler);
90+
EventDataHandlerUtil.<T, V>registerTimedProcessing(this.dataHandler);
7491
}
7592

93+
/**
94+
* Process the event.
95+
*
96+
* @param event {@link IEvent} to count
97+
* @param metaInfo the {@link IMetaInfo} that can hold information about event origin
98+
*
99+
* @throws IllegalStateException if the current instance has been disabled.
100+
* See {@link DataHandlerCounter#close()}
101+
*/
76102
@Override
77103
protected void onEvent(IEvent event, IMetaInfo metaInfo) {
78-
dataHandler.register(event, metaInfo);
104+
if (this.closed) {
105+
throw new IllegalStateException(PdfException.DataHandlerCounterHasBeenDisabled);
106+
}
107+
this.dataHandler.register(event, metaInfo);
108+
}
109+
110+
/**
111+
* Disable all registered hooks and process the left data. Note that after this method
112+
* invocation the {@link DataHandlerCounter#onEvent(IEvent, IMetaInfo)} method would throw
113+
* an exception.
114+
*/
115+
@Override
116+
public void close() {
117+
this.closed = true;
118+
try {
119+
EventDataHandlerUtil.<T, V>disableShutdownHooks(this.dataHandler);
120+
EventDataHandlerUtil.<T, V>disableTimedProcessing(this.dataHandler);
121+
} finally {
122+
this.dataHandler.tryProcessRest();
123+
}
79124
}
80125
}

kernel/src/main/java/com/itextpdf/kernel/counter/data/EventDataHandlerUtil.java

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.kernel.counter.data;
4545

4646
import com.itextpdf.io.LogMessageConstant;
47+
48+
import java.util.concurrent.ConcurrentHashMap;
4749
import org.slf4j.LoggerFactory;
4850

4951
import java.util.Comparator;
@@ -54,6 +56,9 @@ This file is part of the iText (R) project.
5456
*/
5557
public final class EventDataHandlerUtil {
5658

59+
private static final ConcurrentHashMap<Object, Thread> shutdownHooks = new ConcurrentHashMap<>();
60+
private static final ConcurrentHashMap<Object, Thread> scheduledTasks = new ConcurrentHashMap<>();
61+
5762
private EventDataHandlerUtil() {
5863
}
5964

@@ -66,49 +71,100 @@ private EventDataHandlerUtil() {
6671
* @param <V> the data type
6772
*/
6873
public static <T, V extends EventData<T>> void registerProcessAllShutdownHook(final EventDataHandler<T, V> dataHandler) {
74+
if (shutdownHooks.containsKey(dataHandler)) {
75+
// hook already registered
76+
return;
77+
}
78+
Thread shutdownHook = new Thread(dataHandler::tryProcessRest);
79+
shutdownHooks.put(dataHandler, shutdownHook);
80+
6981
try {
70-
Runtime.getRuntime().addShutdownHook(new Thread() {
71-
@Override
72-
public void run() {
73-
dataHandler.tryProcessRest();
74-
}
75-
});
82+
Runtime.getRuntime().addShutdownHook(shutdownHook);
7683
} catch (SecurityException security) {
77-
LoggerFactory.getLogger(EventDataHandlerUtil.class).error(LogMessageConstant.UNABLE_TO_REGISTER_EVENT_DATA_HANDLER_SHUTDOWN_HOOK);
84+
LoggerFactory.getLogger(EventDataHandlerUtil.class)
85+
.error(LogMessageConstant.UNABLE_TO_REGISTER_EVENT_DATA_HANDLER_SHUTDOWN_HOOK);
86+
shutdownHooks.remove(dataHandler);
7887
} catch (Exception ignored) {
7988
//The other exceptions are indicating that ether hook is already registered or hooks are running,
8089
//so there is nothing to do here
8190
}
8291
}
8392

8493
/**
85-
* Creates thread that will try to trigger event processing with time interval from specified {@link EventDataHandler}
94+
* Unregister shutdown hook for {@link EventDataHandler} registered with
95+
* {@link EventDataHandlerUtil#registerProcessAllShutdownHook(EventDataHandler)}.
96+
*
97+
* @param dataHandler the {@link EventDataHandler} for which the hook will be unregistered
98+
* @param <T> the data signature type
99+
* @param <V> the data type
100+
*/
101+
public static <T, V extends EventData<T>> void disableShutdownHooks(final EventDataHandler<T, V> dataHandler) {
102+
Thread toDisable = shutdownHooks.remove(dataHandler);
103+
if (toDisable != null) {
104+
try {
105+
Runtime.getRuntime().removeShutdownHook(toDisable);
106+
} catch (SecurityException security) {
107+
LoggerFactory.getLogger(EventDataHandlerUtil.class)
108+
.error(LogMessageConstant.UNABLE_TO_UNREGISTER_EVENT_DATA_HANDLER_SHUTDOWN_HOOK);
109+
} catch (Exception ignored) {
110+
//The other exceptions are indicating that hooks are running,
111+
//so there is nothing to do here
112+
}
113+
}
114+
}
115+
116+
/**
117+
* Creates thread that will try to trigger event processing with time interval from
118+
* specified {@link EventDataHandler}.
86119
*
87120
* @param dataHandler the {@link EventDataHandler} for which the thread will be registered
88121
* @param <T> the data signature type
89122
* @param <V> the data type
90123
*/
91124
public static <T, V extends EventData<T>> void registerTimedProcessing(final EventDataHandler<T, V> dataHandler) {
92-
Thread thread = new Thread() {
93-
@Override
94-
public void run() {
95-
while (true) {
96-
try {
97-
Thread.sleep(dataHandler.getWaitTime().getTime());
98-
dataHandler.tryProcessNextAsync(false);
99-
} catch (InterruptedException e) {
100-
break;
101-
} catch (Exception any) {
102-
LoggerFactory.getLogger(EventDataHandlerUtil.class).error(LogMessageConstant.UNEXPECTED_EVENT_HANDLER_SERVICE_THREAD_EXCEPTION, any);
103-
break;
104-
}
125+
if (scheduledTasks.containsKey(dataHandler)) {
126+
// task already registered
127+
return;
128+
}
129+
Thread thread = new Thread(() -> {
130+
while (true) {
131+
try {
132+
Thread.sleep(dataHandler.getWaitTime().getTime());
133+
dataHandler.tryProcessNextAsync(false);
134+
} catch (InterruptedException e) {
135+
break;
136+
} catch (Exception any) {
137+
LoggerFactory.getLogger(EventDataHandlerUtil.class)
138+
.error(LogMessageConstant.UNEXPECTED_EVENT_HANDLER_SERVICE_THREAD_EXCEPTION, any);
139+
break;
105140
}
106141
}
107-
};
142+
});
143+
scheduledTasks.put(dataHandler, thread);
108144
thread.setDaemon(true);
109145
thread.start();
110146
}
111147

148+
/**
149+
* Stop the timed processing thread registered with
150+
* {@link EventDataHandlerUtil#registerTimedProcessing(EventDataHandler)}.
151+
*
152+
* @param dataHandler the {@link EventDataHandler} for which the thread will be registered
153+
* @param <T> the data signature type
154+
* @param <V> the data type
155+
*/
156+
public static <T, V extends EventData<T>> void disableTimedProcessing(final EventDataHandler<T, V> dataHandler) {
157+
Thread toDisable = scheduledTasks.remove(dataHandler);
158+
if (toDisable != null) {
159+
try {
160+
toDisable.interrupt();
161+
} catch (SecurityException security) {
162+
LoggerFactory.getLogger(EventDataHandlerUtil.class)
163+
.error(LogMessageConstant.UNABLE_TO_INTERRUPT_THREAD);
164+
}
165+
}
166+
}
167+
112168
/**
113169
* Comparator class that can be used in {@link EventDataCacheComparatorBased}.
114170
* If so, the cache will return {@link EventData} with bigger count first.

0 commit comments

Comments
 (0)