package org.dcache.srm.util; import org.slf4j.MDC; import java.util.Base64; import java.util.Deque; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicLong; import org.dcache.util.NDC; import static com.google.common.base.Strings.nullToEmpty; /** * The SRM Job/request Diagnostic Context, a utility class for working * with dCache NDC. * * The class serves two purposes: * * - It contains a number of static methods for manipulating the NDC. * * - JDC instances capture the Job related NDC values. * These can be applied to other threads. This is useful when using * worker threads that should inherit the context of the task * creation point. * * This class provides AutoCloseable, which allows the try-with-resource * pattern; for example, to temporarily restore a captured context (e.g., when * processing scheduled activity from thread-pool): * * try (JDC ignored = otherJdc.apply()) { * // logging now with otherJdc context * } * // logging now with origin context * * or, when creating a new session ID: * * try (JDC ignored = JDC.createSession("my-session")) { * // logging now with session. * } * // logging with original context * * or simply to roll-back any potential changes to the context: * * try (JDC ignored = new JDC()) { * // activity that could modify JDC * } * // logging now with original context */ public class JDC implements AutoCloseable { // FIXME this value must be the same as dmg.cells.nucleus.CDC.MDC_SESSION // as the mapping JDC --> CDC currently requires this coincidence. public static final String MDC_SESSION = "cells.session"; private static final String _epoc = createEpocString() + ":"; private static final AtomicLong _id = new AtomicLong(); private final NDC _ndc; private final String _session; /** * Captures the cells diagnostic context of the calling thread. */ public JDC() { _session = getSession(); _ndc = NDC.cloneNdc(); } /** * Wrapper around <code>MDC.put</code> and * <code>MDC.remove</code>. <code>value</code> is allowed to e * null. */ private static void setMdc(String key, String value) { if (value != null) { MDC.put(key, value); } else { MDC.remove(key); } } @Override public void close() { apply(); } /** * Applies the saved context to the calling thread. */ public JDC apply() { JDC jdc = new JDC(); setMdc(MDC_SESSION, _session); NDC.set(_ndc); return jdc; } /** * Returns this session's identifier. The value is bound to the current * thread. */ public static String getSession() { return MDC.get(MDC_SESSION); } /** * Sets a session identifier. If session has not been set then the * session is pushed onto the NDC. If the session has already been set * then the existing NDC value is updated with the new session identifier. * * @param id Session identifier. */ public static void setSession(String id) { Deque<String> items = new LinkedList<>(); String oldId = MDC.get(MDC_SESSION); if (oldId != null) { String popped = NDC.pop(); while (popped != null && !popped.equals(oldId)) { items.push(popped); popped = NDC.pop(); } } NDC.push(id); while (items.peek() != null) { NDC.push(items.pop()); } setMdc(MDC_SESSION, id); } /** * Creates a session identifier and pushes this onto the NDC. * The ID has two parts: {@literal <SRM REQUEST>:<SRM OPERATION>}, where * {@literal <SRM REQUEST>} identifies a specific SOAP interaction and * {@literal <SRM OPERATION>} identifies an operation that may span multiple * {@literal <SRM REQUEST>}s. For example, uploading a single file will * have exactly one {@literal <SRM OPERATION>} but multiple * {@literal <SRM REQUEST>}s, consisting of exactly one * {@literal srmPrepareToPut} request, zero or more * {@literal srmStatusOfPutRequest} requests and exactly one * {@literal srmPutDone} request. * * By making this distinction explicit in the Session-ID format, an admin * may search for session IDs that match the complete session-ID when * looking for information about a specific SRM request, or for the * {@literal <SRM OPERATION>}-part when looking for all SRM requests for a * specific SRM operation. * * @param request Information identifying the SRM request * @return a JDC capturing the previous context */ public static JDC createSession(String request) { JDC current = new JDC(); setSession(_epoc + Long.toString(_id.incrementAndGet()) + ":" + request); return current; } /** * Add {@literal <SRM OPERATION>} information to the session ID. * The new session ID is a combination of the existing session ID (if any) * and the suffix. The NDC is updated accordingly. */ public static void appendToSession(String suffix) { setSession(nullToEmpty(getSession()) + ":" + suffix); } private static String createEpocString() { long time = System.currentTimeMillis(); byte hash1 = (byte)(time ^ (time >>> 8) ^ (time >>> 24) ^ (time >>> 40) ^ (time >>> 56)); byte hash2 = (byte)((time >>> 16) ^ (time >>> 32) ^ (time >>> 48)); String id = Base64.getEncoder().encodeToString(new byte[] {hash1, hash2}); int idx = id.indexOf('='); return idx == -1 ? id : id.substring(0, idx); } }