1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.common;
20
21 import static org.mycore.common.events.MCRSessionEvent.Type.activated;
22 import static org.mycore.common.events.MCRSessionEvent.Type.passivated;
23
24 import java.net.InetAddress;
25 import java.net.URI;
26 import java.net.UnknownHostException;
27 import java.util.Collections;
28 import java.util.Hashtable;
29 import java.util.Iterator;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Objects;
36 import java.util.Optional;
37 import java.util.Queue;
38 import java.util.Set;
39 import java.util.UUID;
40 import java.util.concurrent.CompletableFuture;
41 import java.util.concurrent.ExecutorService;
42 import java.util.concurrent.Executors;
43 import java.util.concurrent.ThreadFactory;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.atomic.AtomicInteger;
46 import java.util.function.Supplier;
47
48 import org.apache.logging.log4j.LogManager;
49 import org.apache.logging.log4j.Logger;
50 import org.mycore.common.config.MCRConfiguration2;
51 import org.mycore.common.events.MCRSessionEvent;
52 import org.mycore.common.events.MCRShutdownHandler;
53 import org.mycore.common.events.MCRShutdownHandler.Closeable;
54 import org.mycore.util.concurrent.MCRTransactionableRunnable;
55
56 import com.google.common.util.concurrent.ThreadFactoryBuilder;
57
58
59
60
61
62
63
64
65
66
67 public class MCRSession implements Cloneable {
68
69 private static final URI DEFAULT_URI = URI.create("");
70
71
72 private Map<Object, Object> map = new Hashtable<>();
73
74 @SuppressWarnings("unchecked")
75 private Map.Entry<Object, Object>[] emptyEntryArray = new Map.Entry[0];
76
77 private List<Map.Entry<Object, Object>> mapEntries;
78
79 private boolean mapChanged = true;
80
81 AtomicInteger accessCount;
82
83 AtomicInteger concurrentAccess;
84
85 ThreadLocal<AtomicInteger> currentThreadCount = ThreadLocal.withInitial(AtomicInteger::new);
86
87
88 static Logger LOGGER = LogManager.getLogger(MCRSession.class.getName());
89
90
91 private MCRUserInformation userInformation;
92
93
94 private String language = null;
95
96 private Locale locale = null;
97
98
99 private String sessionID;
100
101 private String ip;
102
103 private long loginTime, lastAccessTime, thisAccessTime, createTime;
104
105 private StackTraceElement[] constructingStackTrace;
106
107 private Optional<URI> firstURI = Optional.empty();
108
109 private ThreadLocal<Throwable> lastActivatedStackTrace = new ThreadLocal<>();
110
111 private ThreadLocal<Queue<Runnable>> onCommitTasks = ThreadLocal.withInitial(LinkedList::new);
112
113 private static ExecutorService COMMIT_SERVICE;
114
115 private static MCRUserInformation guestUserInformation = MCRSystemUserInformation.getGuestInstance();
116
117 static {
118 ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("MCRSession-OnCommitService-#%d")
119 .build();
120 COMMIT_SERVICE = Executors.newFixedThreadPool(4, threadFactory);
121 MCRShutdownHandler.getInstance().addCloseable(new Closeable() {
122
123 @Override
124 public void prepareClose() {
125 COMMIT_SERVICE.shutdown();
126 }
127
128 @Override
129 public int getPriority() {
130 return Integer.MIN_VALUE + 8;
131 }
132
133 @Override
134 public void close() {
135 if (!COMMIT_SERVICE.isTerminated()) {
136 try {
137 COMMIT_SERVICE.awaitTermination(10, TimeUnit.MINUTES);
138 } catch (InterruptedException e) {
139 LOGGER.warn("Error while waiting for shutdown.", e);
140 }
141 }
142 }
143
144 });
145 }
146
147
148
149
150
151 MCRSession() {
152 userInformation = guestUserInformation;
153 setCurrentLanguage(MCRConfiguration2.getString("MCR.Metadata.DefaultLang").orElse(MCRConstants.DEFAULT_LANG));
154 accessCount = new AtomicInteger();
155 concurrentAccess = new AtomicInteger();
156
157 ip = "";
158 sessionID = buildSessionID();
159 MCRSessionMgr.addSession(this);
160
161 LOGGER.debug("MCRSession created {}", sessionID);
162 setLoginTime();
163 createTime = loginTime;
164 Throwable t = new Throwable();
165 t.fillInStackTrace();
166 constructingStackTrace = t.getStackTrace();
167 }
168
169 protected final void setLoginTime() {
170 loginTime = System.currentTimeMillis();
171 lastAccessTime = loginTime;
172 thisAccessTime = loginTime;
173 }
174
175
176
177
178
179 private static String buildSessionID() {
180 return UUID.randomUUID().toString();
181 }
182
183
184
185
186 public String getID() {
187 return sessionID;
188 }
189
190
191
192
193
194
195
196 public Iterator<Object> getObjectsKeyList() {
197 return Collections.unmodifiableSet(map.keySet()).iterator();
198 }
199
200
201
202
203 public List<Map.Entry<Object, Object>> getMapEntries() {
204 if (mapChanged) {
205 mapChanged = false;
206 final Set<Entry<Object, Object>> entrySet = Collections.unmodifiableMap(map).entrySet();
207 final Map.Entry<Object, Object>[] entryArray = entrySet.toArray(emptyEntryArray);
208 mapEntries = List.of(entryArray);
209 }
210 return mapEntries;
211 }
212
213
214 public final String getCurrentLanguage() {
215 return language;
216 }
217
218
219 public final void setCurrentLanguage(String language) {
220 Locale newLocale = Locale.forLanguageTag(language);
221 this.language = language;
222 this.locale = newLocale;
223 }
224
225 public Locale getLocale() {
226 return locale;
227 }
228
229
230 public final void debug() {
231 LOGGER.debug("SessionID = {}", sessionID);
232 LOGGER.debug("UserID = {}", getUserInformation().getUserID());
233 LOGGER.debug("IP = {}", ip);
234 LOGGER.debug("language = {}", language);
235 }
236
237
238 public Object put(Object key, Object value) {
239 mapChanged = true;
240 return map.put(key, value);
241 }
242
243
244 public Object get(Object key) {
245 return map.get(key);
246 }
247
248 public void deleteObject(Object key) {
249 mapChanged = true;
250 map.remove(key);
251 }
252
253
254 public String getCurrentIP() {
255 return ip;
256 }
257
258
259 public final void setCurrentIP(String newip) {
260
261 if (Character.digit(newip.charAt(0), 16) == -1 && newip.charAt(0) != ':') {
262 LOGGER.error("Is not a valid IP address: {}", newip);
263 return;
264 }
265 try {
266 InetAddress inetAddress = InetAddress.getByName(newip);
267 ip = inetAddress.getHostAddress();
268 } catch (UnknownHostException e) {
269 LOGGER.error("Exception while parsing new ip {} using old value.", newip, e);
270 }
271 }
272
273 public final long getLoginTime() {
274 return loginTime;
275 }
276
277 public void close() {
278
279 LOGGER.debug("Remove myself from MCRSession list");
280 MCRSessionMgr.removeSession(this);
281
282 LOGGER.debug("Clearing local map.");
283 map.clear();
284 mapEntries = null;
285 sessionID = null;
286 }
287
288 @Override
289 public String toString() {
290 return "MCRSession[" + getID() + ",user:'" + getUserInformation().getUserID() + "',ip:" + getCurrentIP()
291 + "]";
292 }
293
294 public long getLastAccessedTime() {
295 return lastAccessTime;
296 }
297
298 public void setFirstURI(Supplier<URI> uri) {
299 if (firstURI.isEmpty()) {
300 firstURI = Optional.of(uri.get());
301 }
302 }
303
304
305
306
307
308
309 void activate() {
310 lastAccessTime = thisAccessTime;
311 thisAccessTime = System.currentTimeMillis();
312 accessCount.incrementAndGet();
313 if (currentThreadCount.get().getAndIncrement() == 0) {
314 lastActivatedStackTrace.set(new RuntimeException("This is for debugging purposes only"));
315 fireSessionEvent(activated, concurrentAccess.incrementAndGet());
316 } else {
317 MCRException e = new MCRException(
318 "Cannot activate a Session more than once per thread: " + currentThreadCount.get().get());
319 LOGGER.warn("Too many activate() calls stacktrace:", e);
320 LOGGER.warn("First activate() call stacktrace:", lastActivatedStackTrace.get());
321 }
322 }
323
324
325
326
327
328
329 void passivate() {
330 if (currentThreadCount.get().getAndDecrement() == 1) {
331 lastActivatedStackTrace.set(null);
332 fireSessionEvent(passivated, concurrentAccess.decrementAndGet());
333 } else {
334 LOGGER.debug("deactivate currentThreadCount: {}", currentThreadCount.get().get());
335 }
336 if (firstURI.isEmpty()) {
337 firstURI = Optional.of(DEFAULT_URI);
338 }
339 onCommitTasks.remove();
340 }
341
342
343
344
345
346
347
348
349
350
351 void fireSessionEvent(MCRSessionEvent.Type type, int concurrentAccessors) {
352 MCRSessionEvent event = new MCRSessionEvent(this, type, concurrentAccessors);
353 LOGGER.debug(event);
354 MCRSessionMgr.getListeners().forEach(l -> l.sessionEvent(event));
355 }
356
357 public long getThisAccessTime() {
358 return thisAccessTime;
359 }
360
361 public long getCreateTime() {
362 return createTime;
363 }
364
365
366
367
368 @Deprecated
369 public void beginTransaction() {
370 MCRTransactionHelper.beginTransaction();
371 }
372
373
374
375
376
377 @Deprecated
378 public boolean transactionRequiresRollback() {
379 return MCRTransactionHelper.transactionRequiresRollback();
380 }
381
382
383
384
385 @Deprecated
386 public void commitTransaction() {
387 MCRTransactionHelper.commitTransaction();
388 }
389
390
391
392
393
394 @Deprecated
395 public void rollbackTransaction() {
396 MCRTransactionHelper.rollbackTransaction();
397 }
398
399
400
401
402
403
404 @Deprecated
405 public boolean isTransactionActive() {
406 return MCRTransactionHelper.isTransactionActive();
407 }
408
409 public StackTraceElement[] getConstructingStackTrace() {
410 return constructingStackTrace;
411 }
412
413 public Optional<URI> getFirstURI() {
414 return firstURI;
415 }
416
417
418
419
420 public MCRUserInformation getUserInformation() {
421 return userInformation;
422 }
423
424
425
426
427
428
429 public void setUserInformation(MCRUserInformation userSystemAdapter) {
430
431 if (!isTransitionAllowed(userSystemAdapter)) {
432 throw new IllegalArgumentException("User transition from "
433 + getUserInformation().getUserID()
434 + " to " + userSystemAdapter.getUserID()
435 + " is not permitted within the same session.");
436 }
437 this.userInformation = userSystemAdapter;
438 setLoginTime();
439 }
440
441
442
443
444
445
446 public void onCommit(Runnable task) {
447 this.onCommitTasks.get().offer(Objects.requireNonNull(task));
448 }
449
450 protected void submitOnCommitTasks() {
451 Queue<Runnable> runnables = onCommitTasks.get();
452 onCommitTasks.remove();
453 CompletableFuture.allOf(runnables.stream()
454 .map(r -> new MCRTransactionableRunnable(r, this))
455 .map(MCRSession::toCompletableFuture)
456 .toArray(CompletableFuture[]::new))
457 .join();
458 }
459
460 private static CompletableFuture<?> toCompletableFuture(MCRTransactionableRunnable r) {
461 try {
462 return CompletableFuture.runAsync(r, COMMIT_SERVICE);
463 } catch (RuntimeException e) {
464 LOGGER.error("Could not submit onCommit task. Running it locally.", e);
465 try {
466 r.run();
467 } catch (RuntimeException e2) {
468 LOGGER.fatal("Argh! Could not run task either. This task is lost 😰", e2);
469 }
470 return CompletableFuture.completedFuture(null);
471 }
472 }
473
474 private boolean isTransitionAllowed(MCRUserInformation userSystemAdapter) {
475
476 if (MCRSystemUserInformation.getSuperUserInstance().getUserID().equals(userInformation.getUserID())
477 || MCRSystemUserInformation.getGuestInstance().getUserID().equals(userInformation.getUserID())
478 || MCRSystemUserInformation.getSystemUserInstance().getUserID().equals(userInformation.getUserID())) {
479 return true;
480 }
481
482
483 return MCRSystemUserInformation.getGuestInstance().getUserID().equals(userSystemAdapter.getUserID())
484 || MCRSystemUserInformation.getSystemUserInstance().getUserID().equals(userSystemAdapter.getUserID())
485 || userInformation.getUserID().equals(userSystemAdapter.getUserID());
486 }
487
488 }