• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

talsma-ict / context-propagation / #1555

08 Nov 2024 01:56PM UTC coverage: 94.898% (+3.2%) from 91.679%
#1555

Pull #515

sjoerdtalsma
Replace several fully qualified names by simple names + import.

Signed-off-by: Sjoerd Talsma <sjoerdtalsma@users.noreply.github.com>
Pull Request #515: Cleanup deprecations to develop next major version (v2)

256 of 282 new or added lines in 27 files covered. (90.78%)

837 of 882 relevant lines covered (94.9%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.11
/context-propagation-core/src/main/java/nl/talsmasoftware/context/core/ContextManagers.java
1
/*
2
 * Copyright 2016-2024 Talsma ICT
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *         http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package nl.talsmasoftware.context.core;
17

18
import nl.talsmasoftware.context.api.Context;
19
import nl.talsmasoftware.context.api.ContextManager;
20
import nl.talsmasoftware.context.api.ContextSnapshot;
21
import nl.talsmasoftware.context.api.ContextSnapshot.Reactivation;
22

23
import java.util.List;
24
import java.util.logging.Level;
25
import java.util.logging.Logger;
26

27
/**
28
 * Core implementation to allow {@link #createContextSnapshot() taking a snapshot of all contexts}.
29
 *
30
 * <p>
31
 * Such a {@link ContextSnapshot snapshot} can be passed to a background task to allow the context to be
32
 * {@link ContextSnapshot#reactivate() reactivated} in that background thread, until it gets
33
 * {@link Context#close() closed} again (preferably in a <code>try-with-resources</code> construct).
34
 *
35
 * @author Sjoerd Talsma
36
 * @since 1.1.0
37
 */
38
public final class ContextManagers {
39
    private static final Logger LOGGER = Logger.getLogger(ContextManagers.class.getName());
1✔
40

41
    /**
42
     * Private constructor to avoid instantiation of this class.
43
     */
44
    private ContextManagers() {
1✔
45
        throw new UnsupportedOperationException();
1✔
46
    }
47

48
    /**
49
     * This method is able to create a 'snapshot' from the current
50
     * {@link ContextManager#getActiveContextValue() active context value}
51
     * from <em>all known {@link ContextManager}</em> implementations.
52
     *
53
     * <p>
54
     * This snapshot is returned as a single object that can be temporarily
55
     * {@link ContextSnapshot#reactivate() reactivated}. Don't forget to {@link Context#close() close} the reactivated
56
     * context once you're done, preferably in a <code>try-with-resources</code> construct.
57
     *
58
     * @return A new snapshot that can be reactivated elsewhere (e.g. a background thread)
59
     * within a try-with-resources construct.
60
     */
61
    @SuppressWarnings("rawtypes")
62
    public static ContextSnapshot createContextSnapshot() {
63
        final long start = System.nanoTime();
1✔
64
        final List<ContextManager> managers = ServiceCache.cached(ContextManager.class); // Cached list is immutable
1✔
65
        final Object[] values = new Object[managers.size()];
1✔
66

67
        for (int i = 0; i < values.length; i++) {
1✔
68
            final ContextManager manager = managers.get(i);
1✔
69
            long managerStart = System.nanoTime();
1✔
70
            try {
71
                values[i] = getActiveContextValue(manager);
1✔
72
                Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "getActiveContext");
1✔
73
            } catch (RuntimeException rte) {
1✔
74
                LOGGER.log(Level.WARNING, "Error obtaining active context from " + manager + " (in thread " + Thread.currentThread().getName() + ").", rte);
1✔
75
                Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "getActiveContext.exception");
1✔
76
            }
1✔
77
        }
78

79
        final ContextSnapshotImpl result = new ContextSnapshotImpl(managers, values);
1✔
80
        if (managers.isEmpty() && LOGGER.isLoggable(Level.FINER)) {
1✔
NEW
81
            LOGGER.finer(result + " was created but no ContextManagers were found! "
×
NEW
82
                    + " Thead=" + Thread.currentThread()
×
NEW
83
                    + ", ContextClassLoader=" + Thread.currentThread().getContextClassLoader());
×
84
        }
85
        Timers.timed(System.nanoTime() - start, ContextManagers.class, "createContextSnapshot");
1✔
86
        return result;
1✔
87
    }
88

89
    /**
90
     * Clears all active contexts from the current thread.
91
     *
92
     * <p>
93
     * Contexts that are 'stacked' (i.e. restore the previous state upon close) should be
94
     * closed in a way that includes all 'parent' contexts as well.
95
     *
96
     * <p>
97
     * This operation is not intended to be used by general application code as it likely breaks any 'stacked'
98
     * active context that surrounding code may depend upon.
99
     * Appropriate use includes thread management, where threads are reused by some pooling
100
     * mechanism. For example, it is considered safe to clear the context when obtaining a 'fresh' thread from a
101
     * thread pool (as no context expectations should exist at that point).
102
     * An even better strategy would be to clear the context right before returning a used thread to the pool
103
     * as this will allow any unclosed contexts to be garbage collected. Besides preventing contextual issues,
104
     * this reduces the risk of memory leaks by unbalanced context calls.
105
     */
106
    public static void clearActiveContexts() {
107
        final long start = System.nanoTime();
1✔
108
        Long managerStart = null;
1✔
109
        for (ContextManager<?> manager : ServiceCache.cached(ContextManager.class)) {
1✔
110
            managerStart = System.nanoTime();
1✔
111
            try {
112
                clear(manager);
1✔
113
                Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "clear");
1✔
NEW
114
            } catch (RuntimeException rte) {
×
NEW
115
                LOGGER.log(Level.WARNING, "Error clearing active context from " + manager + ".", rte);
×
NEW
116
                ServiceCache.clear();
×
NEW
117
                Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "clear.exception");
×
118
            }
1✔
119
        }
1✔
120
        if (managerStart == null && LOGGER.isLoggable(Level.FINER)) {
1✔
NEW
121
            LOGGER.finer("No ContextManagers were cleared because none were found! "
×
NEW
122
                    + " Thead=" + Thread.currentThread()
×
NEW
123
                    + ", ContextClassLoader=" + Thread.currentThread().getContextClassLoader());
×
124
        }
125
        Timers.timed(System.nanoTime() - start, ContextManagers.class, "clearActiveContexts");
1✔
126
    }
1✔
127

128
    /**
129
     * Override the {@linkplain ClassLoader} used to lookup {@linkplain ContextManager contextmanagers}.
130
     * <p>
131
     * Normally, taking a snapshot uses the {@linkplain Thread#getContextClassLoader() Context ClassLoader} from the
132
     * {@linkplain Thread#currentThread() current thread} to look up all {@linkplain ContextManager context managers}.
133
     * It is possible to configure a fixed, single classloader in your application for looking up the context managers.
134
     * <p>
135
     * Using this method to specify a fixed classloader will only impact
136
     * new {@linkplain ContextSnapshot context snapshots}. Existing snapshots will not be impacted.
137
     * <p>
138
     * <strong>Notes:</strong><br>
139
     * <ul>
140
     * <li>Please be aware that this configuration is global!
141
     * <li>This will also affect the lookup of
142
     * {@linkplain nl.talsmasoftware.context.api.ContextTimer context timers}
143
     * </ul>
144
     *
145
     * @param classLoader The single, fixed ClassLoader to use for finding context managers.
146
     *                    Specify {@code null} to restore the default behaviour.
147
     * @since 1.0.5
148
     */
149
    public static synchronized void useClassLoader(ClassLoader classLoader) {
150
        ServiceCache.useClassLoader(classLoader);
1✔
151
    }
1✔
152

153
    private static Object getActiveContextValue(ContextManager<?> manager) {
154
        final Object activeContextValue = manager.getActiveContextValue();
1✔
155
        LOGGER.finest(() -> activeContextValue == null
1✔
NEW
156
                ? "There is no active context value for " + manager + " (in thread " + Thread.currentThread().getName() + ")."
×
NEW
157
                : "Active context value of " + manager + " in " + Thread.currentThread().getName() + ": " + activeContextValue);
×
158
        return activeContextValue;
1✔
159
    }
160

161
    private static void clear(ContextManager<?> manager) {
162
        manager.clear();
1✔
163
        LOGGER.finest(() -> "Active context of " + manager + " was cleared.");
1✔
164
    }
1✔
165

166
    /**
167
     * Implementation of the <code>createContextSnapshot</code> functionality that can reactivate all values from the
168
     * snapshot in each corresponding {@link ContextManager}.
169
     */
170
    @SuppressWarnings("rawtypes")
171
    private static final class ContextSnapshotImpl implements ContextSnapshot {
172
        // TODO extract this inner class?
173
        private final List<ContextManager> managers;
174
        private final Object[] values;
175

176
        private ContextSnapshotImpl(List<ContextManager> managers, Object[] values) {
1✔
177
            this.managers = managers;
1✔
178
            this.values = values;
1✔
179
        }
1✔
180

181
        public Reactivation reactivate() {
182
            final long start = System.nanoTime();
1✔
183
            final Context[] reactivatedContexts = new Context[managers.size()];
1✔
184

185
            try {
186
                for (int i = 0; i < values.length; i++) {
1✔
187
                    reactivatedContexts[i] = reactivate(managers.get(i), values[i]);
1✔
188
                }
189

190
                ReactivationImpl reactivation = new ReactivationImpl(reactivatedContexts);
1✔
191
                Timers.timed(System.nanoTime() - start, ContextSnapshot.class, "reactivate");
1✔
192
                return reactivation;
1✔
193
            } catch (RuntimeException reactivationException) {
1✔
194
                // TODO think about simplifying by catching & handling in reactivate(manager, value) method
195
                for (Context alreadyReactivated : reactivatedContexts) {
1✔
196
                    if (alreadyReactivated != null) try {
1✔
197
                        if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
198
                            LOGGER.finest("Snapshot reactivation failed! " +
×
199
                                    "Closing already reactivated context: " + alreadyReactivated + ".");
200
                        }
201
                        alreadyReactivated.close();
1✔
NEW
202
                    } catch (RuntimeException rte) {
×
NEW
203
                        reactivationException.addSuppressed(rte);
×
204
                    }
1✔
205
                }
206
                ServiceCache.clear();
1✔
207
                throw reactivationException;
1✔
208
            }
209
        }
210

211
        @SuppressWarnings("unchecked") // As we got the values from the managers themselves, they must also accept them!
212
        private Context reactivate(ContextManager contextManager, Object snapshotValue) {
213
            long start = System.nanoTime();
1✔
214
            if (snapshotValue == null) return null;
1✔
215
            Context reactivated = contextManager.initializeNewContext(snapshotValue);
1✔
216
            if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
217
                LOGGER.finest("Context reactivated from snapshot by " + contextManager + ": " + reactivated + ".");
×
218
            }
219
            Timers.timed(System.nanoTime() - start, contextManager.getClass(), "initializeNewContext");
1✔
220
            return reactivated;
1✔
221
        }
222

223
        @Override
224
        public String toString() {
225
            return "ContextSnapshot{size=" + managers.size() + '}';
1✔
226
        }
227
    }
228

229
    /**
230
     * Implementation of the reactivated 'container' context that closes all reactivated contexts
231
     * when it is closed itself.<br>
232
     * This context contains no meaningful value in itself and purely exists to close the reactivated contexts.
233
     */
234
    @SuppressWarnings("rawtypes")
235
    private static final class ReactivationImpl implements Reactivation {
236
        // TODO extract this inner class?
237
        private final Context[] reactivated;
238

239
        private ReactivationImpl(Context[] reactivated) {
1✔
240
            this.reactivated = reactivated;
1✔
241
        }
1✔
242

243
        public void close() {
244
            RuntimeException closeException = null;
1✔
245
            // close in reverse order of reactivation
246
            for (int i = this.reactivated.length - 1; i >= 0; i--) {
1✔
247
                Context<?> reactivated = this.reactivated[i];
1✔
248
                if (reactivated != null) try {
1✔
249
                    reactivated.close();
1✔
250
                } catch (RuntimeException rte) {
1✔
251
                    if (closeException == null) closeException = rte;
1✔
NEW
252
                    else closeException.addSuppressed(rte);
×
253
                }
1✔
254
            }
255
            if (closeException != null) throw closeException;
1✔
256
        }
1✔
257

258
        @Override
259
        public String toString() {
260
            return "ContextSnapshot.Reactivation{size=" + reactivated.length + '}';
1✔
261
        }
262
    }
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc