AAI-1523 Batch reformat aai-core
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / logging / LoggingContext.java
index 50af1c6..f79025a 100644 (file)
  * limitations under the License.
  * ============LICENSE_END=========================================================
  */
+
 package org.onap.aai.logging;
 
+import com.att.eelf.configuration.EELFLogger;
+import com.att.eelf.configuration.EELFManager;
+
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.HashMap;
@@ -33,394 +37,384 @@ import org.json.JSONObject;
 import org.onap.aai.exceptions.AAIException;
 import org.slf4j.MDC;
 
-import com.att.eelf.configuration.EELFLogger;
-import com.att.eelf.configuration.EELFManager;
-
 public class LoggingContext {
 
-       public enum StatusCode {
-               COMPLETE,
-               ERROR
-       }
-
-       private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(LoggingContext.class);
-
-       private static final String PREVIOUS_CONTEXTS_KEY = "_PREVIOUS_CONTEXTS";
-       
-       //Response codes from Logging Guidelines
-       public static final String SUCCESS = "0";
-       public static final String PERMISSION_ERROR = "100";
-       public static final String AVAILABILITY_TIMEOUT_ERROR = "200";
-       public static final String DATA_ERROR = "300";
-       public static final String SCHEMA_ERROR = "400";
-       public static final String BUSINESS_PROCESS_ERROR = "500";
-       public static final String UNKNOWN_ERROR = "900";
-       
-       public static final Map<String, String> responseMap = new HashMap();
-        
+    public enum StatusCode {
+        COMPLETE, ERROR
+    }
+
+    private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(LoggingContext.class);
+
+    private static final String PREVIOUS_CONTEXTS_KEY = "_PREVIOUS_CONTEXTS";
+
+    // Response codes from Logging Guidelines
+    public static final String SUCCESS = "0";
+    public static final String PERMISSION_ERROR = "100";
+    public static final String AVAILABILITY_TIMEOUT_ERROR = "200";
+    public static final String DATA_ERROR = "300";
+    public static final String SCHEMA_ERROR = "400";
+    public static final String BUSINESS_PROCESS_ERROR = "500";
+    public static final String UNKNOWN_ERROR = "900";
+
+    public static final Map<String, String> responseMap = new HashMap();
+
     static {
         responseMap.put(SUCCESS, "Success");
         responseMap.put(UNKNOWN_ERROR, "Unknown error");
     }
-       // Specific Log Event Fields
-       public static enum LoggingField {
-               START_TIME("startTime"),
-               REQUEST_ID("requestId"),
-               SERVICE_INSTANCE_ID("serviceInstanceId"),
-               SERVER_NAME("serverName"),
-               SERVICE_NAME("serviceName"),
-               PARTNER_NAME("partnerName"),
-               STATUS_CODE("statusCode"),
-               RESPONSE_CODE("responseCode"),
-               RESPONSE_DESCRIPTION("responseDescription"),
-               INSTANCE_UUID("instanceUUID"),
-               SEVERITY("severity"),
-               SERVER_IP_ADDRESS("serverIpAddress"),
-               ELAPSED_TIME("elapsedTime"),
-               SERVER("server"),
-               CLIENT_IP_ADDRESS("clientIpAddress"),
-               UNUSED("unused"),
-               PROCESS_KEY("processKey"),
-               CUSTOM_FIELD_1("customField1"),
-               CUSTOM_FIELD_2("customField2"),
-               CUSTOM_FIELD_3("customField3"),
-               CUSTOM_FIELD_4("customField4"),
-               
-               // Specific Metric Log Event Fields
-               TARGET_ENTITY("targetEntity"),
-               TARGET_SERVICE_NAME("targetServiceName"),
-               //A&AI Specific Log Event Fields
-               COMPONENT("component"),
-               STOP_WATCH_START("stopWatchStart");
-
-               private final String text;
-
-               private LoggingField(final String text) {
-                       this.text = text;
-               }
-
-               public String toString() {
-                       return text;
-               }
-       }
-
-
-       public static void init() {
-               LoggingContext.clear();
-               LoggingContext.startTime();
-               LoggingContext.server();
-               LoggingContext.serverIpAddress();
-       }
-
-       public static void startTime() {
-               MDC.put(LoggingField.START_TIME.toString(), LogFormatTools.getCurrentDateTime());
-       }
-
-       public static UUID requestId() {
-               final String sUuid = MDC.get(LoggingField.REQUEST_ID.toString());
-
-               if (sUuid == null) return null;
-
-               return UUID.fromString(sUuid);
-       }
-
-       public static void requestId(UUID requestId) {
-               MDC.put(LoggingField.REQUEST_ID.toString(), requestId.toString());
-       }
-
-       public static void requestId(String requestId) {
-               try {
-                       if (requestId.contains(":")) {
-                               String[] uuidParts = requestId.split(":");
-                               requestId = uuidParts[0];
-                       }
-                       MDC.put(LoggingField.REQUEST_ID.toString(), UUID.fromString(requestId).toString());
-               } catch (IllegalArgumentException e) {
-                       final UUID generatedRequestUuid = UUID.randomUUID();
-                       MDC.put(LoggingField.REQUEST_ID.toString(), generatedRequestUuid.toString());
-                       LoggingContext.save();
-                       // set response code to 0 since we don't know what the outcome of this request is yet
-                       String responseCode = LoggingContext.DATA_ERROR;
-                       LoggingContext.responseCode(responseCode);
-                       LoggingContext.responseDescription("Unable to use UUID " + requestId + " (Not formatted properly) ");
-                       LoggingContext.statusCode(StatusCode.ERROR);
-                       
-                       LOGGER.warn("Using generated UUID=" + generatedRequestUuid);
-                       LoggingContext.restore();
-
-               }
-       }
-
-       public static void serviceInstanceId(String serviceInstanceId) {
-               MDC.put(LoggingField.SERVICE_INSTANCE_ID.toString(), serviceInstanceId);
-       }
-
-       public static void serverName(String serverName) {
-               MDC.put(LoggingField.SERVER_NAME.toString(), serverName);
-       }
-
-       public static void serviceName(String serviceName) {
-               MDC.put(LoggingField.SERVICE_NAME.toString(), serviceName);
-       }
-
-       public static void partnerName(String partnerName) {
-               MDC.put(LoggingField.PARTNER_NAME.toString(), partnerName);
-       }
-
-       public static void statusCode(StatusCode statusCode) {
-               MDC.put(LoggingField.STATUS_CODE.toString(), statusCode.toString());
-       }
-
-       public static String responseCode() {
-               return (String) MDC.get(LoggingField.RESPONSE_CODE.toString());
-       }
-
-       public static void responseCode(String responseCode) {
-               MDC.put(LoggingField.RESPONSE_CODE.toString(), responseCode);
-       }
-
-       public static void responseDescription(String responseDescription) {
-               MDC.put(LoggingField.RESPONSE_DESCRIPTION.toString(), responseDescription);
-       }
-
-       public static Object instanceUuid() {
-               return UUID.fromString(MDC.get(LoggingField.INSTANCE_UUID.toString()));
-       }
-
-       public static void instanceUuid(UUID instanceUuid) {
-               MDC.put(LoggingField.INSTANCE_UUID.toString(), instanceUuid.toString());
-       }
-
-       public static void severity(int severity) {
-               MDC.put(LoggingField.SEVERITY.toString(), String.valueOf(severity));
-       }
-
-       public static void successStatusFields() {
-               responseCode(SUCCESS);
-               statusCode(StatusCode.COMPLETE);
-               responseDescription("Success");
-       }
-       private static void serverIpAddress() {
-               try {
-                       MDC.put(LoggingField.SERVER_IP_ADDRESS.toString(), InetAddress.getLocalHost().getHostAddress());
-               } catch (UnknownHostException e) {
-                       LOGGER.warn("Unable to resolve server IP address - will not be displayed in logged events");
-               }
-       }
-
-       public static void elapsedTime(long elapsedTime, TimeUnit timeUnit) {
-               MDC.put(LoggingField.ELAPSED_TIME.toString(), String.valueOf(TimeUnit.MILLISECONDS.convert(elapsedTime, timeUnit)));
-       }
-
-       private static void server() {
-               try {
-                       MDC.put(LoggingField.SERVER.toString(),  InetAddress.getLocalHost().getCanonicalHostName());
-               } catch (UnknownHostException e) {
-                       LOGGER.warn("Unable to resolve server IP address - hostname will not be displayed in logged events");
-               }
-       }
-
-       public static void clientIpAddress(InetAddress clientIpAddress) {
-               MDC.put(LoggingField.CLIENT_IP_ADDRESS.toString(), clientIpAddress.getHostAddress());
-       }
-
-       public static void clientIpAddress(String clientIpAddress) {
-               try {
-                       MDC.put(LoggingField.CLIENT_IP_ADDRESS.toString(), InetAddress.getByName(clientIpAddress).getHostAddress());
-               } catch (UnknownHostException e) {
-                       //Ignore, will not be thrown since InetAddress.getByName(String) only
-                       //checks the validity of the passed in string
-               }
-       }
-
-       public static void unused(String unused) {
-               LOGGER.warn("Using field '" + LoggingField.UNUSED + "' (seems like this should go unused...)");
-               MDC.put(LoggingField.UNUSED.toString(), unused);
-       }
-
-       public static void processKey(String processKey) {
-               MDC.put(LoggingField.PROCESS_KEY.toString(), processKey);
-       }
-
-       public static String customField1() {
-               return MDC.get(LoggingField.CUSTOM_FIELD_1.toString());
-       }
-
-       public static void customField1(String customField1) {
-               MDC.put(LoggingField.CUSTOM_FIELD_1.toString(), customField1);
-       }
-
-       public static void customField2(String customField2) {
-               MDC.put(LoggingField.CUSTOM_FIELD_2.toString(), customField2);
-       }
-
-       public static void customField3(String customField3) {
-               MDC.put(LoggingField.CUSTOM_FIELD_3.toString(), customField3);
-       }
-
-       public static void customField4(String customField4) {
-               MDC.put(LoggingField.CUSTOM_FIELD_4.toString(), customField4);
-       }
-
-       public static void component(String component) {
-               MDC.put(LoggingField.COMPONENT.toString(), component);
-       }
-
-       public static void targetEntity(String targetEntity) {
-               MDC.put(LoggingField.TARGET_ENTITY.toString(), targetEntity);
-       }
-
-       public static void targetServiceName(String targetServiceName) {
-               MDC.put(LoggingField.TARGET_SERVICE_NAME.toString(), targetServiceName);
-       }
-
-       public static boolean isStopWatchStarted() {
-               final String rawStopWatchStart = MDC.get(LoggingField.STOP_WATCH_START.toString());
-               if (rawStopWatchStart == null) {
-                       return false;
-               }
-               return true;
-       }
-       public static void stopWatchStart() {
-               MDC.put(LoggingField.STOP_WATCH_START.toString(), String.valueOf(System.nanoTime()));
-       }
-
-       public static double stopWatchStop() {
-               final long stopWatchEnd = System.nanoTime();
-               final String rawStopWatchStart = MDC.get(LoggingField.STOP_WATCH_START.toString());
-
-               if (rawStopWatchStart == null) throw new StopWatchNotStartedException();
-
-               final Long stopWatchStart = Long.valueOf(rawStopWatchStart);
-
-               MDC.remove(LoggingField.STOP_WATCH_START.toString());
-
-               final double elapsedTimeMillis = (stopWatchEnd - stopWatchStart) / 1000.0 / 1000.0;
-
-               LoggingContext.elapsedTime((long) elapsedTimeMillis, TimeUnit.MILLISECONDS);
-
-               return elapsedTimeMillis;
-       }
-
-       public static void put(String key, String value) {
-               MDC.put(key, value);
-       }
-
-       public static void clear() {
-               MDC.clear();
-       }
-
-       public static void remove(String key) {
-               MDC.remove(key);
-       }
-
-       public static void save() {
-               final JSONObject context = new JSONObject();
-
-               for (LoggingField field : LoggingField.values()) {
-                       if (field == LoggingField.ELAPSED_TIME) continue;
-
-                       try {
-                               context.put(field.toString(), MDC.get(field.toString()));
-                       } catch (JSONException e) {
-                               //Ignore - only occurs when the key is null (which can't happen)
-                               //                      or the value is invalid (everything is converted to a string
-                               //                      before it get put() to the MDC)
-                       }
-               }
-
-               final String rawJsonArray = MDC.get(PREVIOUS_CONTEXTS_KEY);
-
-               if (rawJsonArray == null) {
-                       final JSONArray stack = new JSONArray()
-                                                                                       .put(context);
-
-                       MDC.put(PREVIOUS_CONTEXTS_KEY, stack.toString());
-               } else {
-                       try {
-                               final JSONArray stack = new JSONArray(rawJsonArray)
-                                                                                               .put(context);
-
-                               MDC.put(PREVIOUS_CONTEXTS_KEY, stack.toString());
-                       } catch (JSONException e) {
-                               //Ignore
-                       }
-               }
-       }
-
-       public static void restore() {
-               
-               final String rawPreviousContexts = MDC.get(PREVIOUS_CONTEXTS_KEY);
-       
-               if (rawPreviousContexts == null) {
-                       throw new LoggingContextNotExistsException();
-               }
-
-               try {
-                       final JSONArray previousContexts = new JSONArray(rawPreviousContexts);
-                       final JSONObject previousContext = previousContexts.getJSONObject(previousContexts.length() - 1);
-
-                       @SuppressWarnings("unchecked")
-                       final Iterator<String> keys = previousContext.keys();
-                       boolean foundElapsedTime = false;
-                       while (keys.hasNext()) {
-                               final String key = keys.next();
-                               if (LoggingField.ELAPSED_TIME.toString().equals(key)) {
-                                       foundElapsedTime = true;
-                               }
-                               try {
-                                       MDC.put(key, previousContext.getString(key));
-                               } catch (JSONException e) {
-                                       //Ignore, only occurs when the key is null (cannot happen)
-                                       //                      or the value is invalid (they are all strings)
-                               }
-                       }
-                       if ( !foundElapsedTime ) {
-                               MDC.remove(LoggingField.ELAPSED_TIME.toString());
-                       }
-                       MDC.put(PREVIOUS_CONTEXTS_KEY, removeLast(previousContexts).toString());
-               } catch (JSONException e) {
-                       //Ignore, the previousContext is serialized from a JSONObject
-               }
-       }
-       public static void restoreIfPossible() {
-               try {
-                       restore();
-               }
-               catch (LoggingContextNotExistsException e) {
-                       //Ignore
-               }
-       }
-
-       /**
-        * AJSC declares an ancient version of org.json:json in one of the parent POMs of this project.
-        * I tried to update our version of that library in our POM, but it's ignored because of the way
-        * AJSC has organized their <dependencies>.  Had they put it into the <dependencyManagement> section,
-        * this method would not be necessary.
-        */
-       private static JSONArray removeLast(JSONArray previousContexts) {
-               final JSONArray result = new JSONArray();
-
-               for (int i = 0; i < previousContexts.length() - 1; i++) {
-                       try {
-                               result.put(previousContexts.getJSONObject(i));
-                       } catch (JSONException e) {
-                               //Ignore - not possible
-                       }
-               }
-
-               return result;
-       }
-
-       public static Map<String, String> getCopy() {
-               final Map<String, String> copy = new HashMap<String, String> ();
-
-               for (LoggingField field : LoggingField.values()) {
-                       final String value = MDC.get(field.toString());
-
-                       if (value != null) copy.put(field.toString(), value);
-               }
-
-               return copy;
-       }
+
+    // Specific Log Event Fields
+    public static enum LoggingField {
+        START_TIME("startTime"), REQUEST_ID("requestId"), SERVICE_INSTANCE_ID("serviceInstanceId"), SERVER_NAME(
+                "serverName"), SERVICE_NAME("serviceName"), PARTNER_NAME("partnerName"), STATUS_CODE(
+                        "statusCode"), RESPONSE_CODE("responseCode"), RESPONSE_DESCRIPTION(
+                                "responseDescription"), INSTANCE_UUID("instanceUUID"), SEVERITY(
+                                        "severity"), SERVER_IP_ADDRESS(
+                                                "serverIpAddress"), ELAPSED_TIME("elapsedTime"), SERVER(
+                                                        "server"), CLIENT_IP_ADDRESS("clientIpAddress"), UNUSED(
+                                                                "unused"), PROCESS_KEY("processKey"), CUSTOM_FIELD_1(
+                                                                        "customField1"), CUSTOM_FIELD_2(
+                                                                                "customField2"), CUSTOM_FIELD_3(
+                                                                                        "customField3"), CUSTOM_FIELD_4(
+                                                                                                "customField4"),
+
+        // Specific Metric Log Event Fields
+        TARGET_ENTITY("targetEntity"), TARGET_SERVICE_NAME("targetServiceName"),
+        // A&AI Specific Log Event Fields
+        COMPONENT("component"), STOP_WATCH_START("stopWatchStart");
+
+        private final String text;
+
+        private LoggingField(final String text) {
+            this.text = text;
+        }
+
+        public String toString() {
+            return text;
+        }
+    }
+
+    public static void init() {
+        LoggingContext.clear();
+        LoggingContext.startTime();
+        LoggingContext.server();
+        LoggingContext.serverIpAddress();
+    }
+
+    public static void startTime() {
+        MDC.put(LoggingField.START_TIME.toString(), LogFormatTools.getCurrentDateTime());
+    }
+
+    public static UUID requestId() {
+        final String sUuid = MDC.get(LoggingField.REQUEST_ID.toString());
+
+        if (sUuid == null)
+            return null;
+
+        return UUID.fromString(sUuid);
+    }
+
+    public static void requestId(UUID requestId) {
+        MDC.put(LoggingField.REQUEST_ID.toString(), requestId.toString());
+    }
+
+    public static void requestId(String requestId) {
+        try {
+            if (requestId.contains(":")) {
+                String[] uuidParts = requestId.split(":");
+                requestId = uuidParts[0];
+            }
+            MDC.put(LoggingField.REQUEST_ID.toString(), UUID.fromString(requestId).toString());
+        } catch (IllegalArgumentException e) {
+            final UUID generatedRequestUuid = UUID.randomUUID();
+            MDC.put(LoggingField.REQUEST_ID.toString(), generatedRequestUuid.toString());
+            LoggingContext.save();
+            // set response code to 0 since we don't know what the outcome of this request is yet
+            String responseCode = LoggingContext.DATA_ERROR;
+            LoggingContext.responseCode(responseCode);
+            LoggingContext.responseDescription("Unable to use UUID " + requestId + " (Not formatted properly) ");
+            LoggingContext.statusCode(StatusCode.ERROR);
+
+            LOGGER.warn("Using generated UUID=" + generatedRequestUuid);
+            LoggingContext.restore();
+
+        }
+    }
+
+    public static void serviceInstanceId(String serviceInstanceId) {
+        MDC.put(LoggingField.SERVICE_INSTANCE_ID.toString(), serviceInstanceId);
+    }
+
+    public static void serverName(String serverName) {
+        MDC.put(LoggingField.SERVER_NAME.toString(), serverName);
+    }
+
+    public static void serviceName(String serviceName) {
+        MDC.put(LoggingField.SERVICE_NAME.toString(), serviceName);
+    }
+
+    public static void partnerName(String partnerName) {
+        MDC.put(LoggingField.PARTNER_NAME.toString(), partnerName);
+    }
+
+    public static void statusCode(StatusCode statusCode) {
+        MDC.put(LoggingField.STATUS_CODE.toString(), statusCode.toString());
+    }
+
+    public static String responseCode() {
+        return (String) MDC.get(LoggingField.RESPONSE_CODE.toString());
+    }
+
+    public static void responseCode(String responseCode) {
+        MDC.put(LoggingField.RESPONSE_CODE.toString(), responseCode);
+    }
+
+    public static void responseDescription(String responseDescription) {
+        MDC.put(LoggingField.RESPONSE_DESCRIPTION.toString(), responseDescription);
+    }
+
+    public static Object instanceUuid() {
+        return UUID.fromString(MDC.get(LoggingField.INSTANCE_UUID.toString()));
+    }
+
+    public static void instanceUuid(UUID instanceUuid) {
+        MDC.put(LoggingField.INSTANCE_UUID.toString(), instanceUuid.toString());
+    }
+
+    public static void severity(int severity) {
+        MDC.put(LoggingField.SEVERITY.toString(), String.valueOf(severity));
+    }
+
+    public static void successStatusFields() {
+        responseCode(SUCCESS);
+        statusCode(StatusCode.COMPLETE);
+        responseDescription("Success");
+    }
+
+    private static void serverIpAddress() {
+        try {
+            MDC.put(LoggingField.SERVER_IP_ADDRESS.toString(), InetAddress.getLocalHost().getHostAddress());
+        } catch (UnknownHostException e) {
+            LOGGER.warn("Unable to resolve server IP address - will not be displayed in logged events");
+        }
+    }
+
+    public static void elapsedTime(long elapsedTime, TimeUnit timeUnit) {
+        MDC.put(LoggingField.ELAPSED_TIME.toString(),
+                String.valueOf(TimeUnit.MILLISECONDS.convert(elapsedTime, timeUnit)));
+    }
+
+    private static void server() {
+        try {
+            MDC.put(LoggingField.SERVER.toString(), InetAddress.getLocalHost().getCanonicalHostName());
+        } catch (UnknownHostException e) {
+            LOGGER.warn("Unable to resolve server IP address - hostname will not be displayed in logged events");
+        }
+    }
+
+    public static void clientIpAddress(InetAddress clientIpAddress) {
+        MDC.put(LoggingField.CLIENT_IP_ADDRESS.toString(), clientIpAddress.getHostAddress());
+    }
+
+    public static void clientIpAddress(String clientIpAddress) {
+        try {
+            MDC.put(LoggingField.CLIENT_IP_ADDRESS.toString(), InetAddress.getByName(clientIpAddress).getHostAddress());
+        } catch (UnknownHostException e) {
+            // Ignore, will not be thrown since InetAddress.getByName(String) only
+            // checks the validity of the passed in string
+        }
+    }
+
+    public static void unused(String unused) {
+        LOGGER.warn("Using field '" + LoggingField.UNUSED + "' (seems like this should go unused...)");
+        MDC.put(LoggingField.UNUSED.toString(), unused);
+    }
+
+    public static void processKey(String processKey) {
+        MDC.put(LoggingField.PROCESS_KEY.toString(), processKey);
+    }
+
+    public static String customField1() {
+        return MDC.get(LoggingField.CUSTOM_FIELD_1.toString());
+    }
+
+    public static void customField1(String customField1) {
+        MDC.put(LoggingField.CUSTOM_FIELD_1.toString(), customField1);
+    }
+
+    public static void customField2(String customField2) {
+        MDC.put(LoggingField.CUSTOM_FIELD_2.toString(), customField2);
+    }
+
+    public static void customField3(String customField3) {
+        MDC.put(LoggingField.CUSTOM_FIELD_3.toString(), customField3);
+    }
+
+    public static void customField4(String customField4) {
+        MDC.put(LoggingField.CUSTOM_FIELD_4.toString(), customField4);
+    }
+
+    public static void component(String component) {
+        MDC.put(LoggingField.COMPONENT.toString(), component);
+    }
+
+    public static void targetEntity(String targetEntity) {
+        MDC.put(LoggingField.TARGET_ENTITY.toString(), targetEntity);
+    }
+
+    public static void targetServiceName(String targetServiceName) {
+        MDC.put(LoggingField.TARGET_SERVICE_NAME.toString(), targetServiceName);
+    }
+
+    public static boolean isStopWatchStarted() {
+        final String rawStopWatchStart = MDC.get(LoggingField.STOP_WATCH_START.toString());
+        if (rawStopWatchStart == null) {
+            return false;
+        }
+        return true;
+    }
+
+    public static void stopWatchStart() {
+        MDC.put(LoggingField.STOP_WATCH_START.toString(), String.valueOf(System.nanoTime()));
+    }
+
+    public static double stopWatchStop() {
+        final long stopWatchEnd = System.nanoTime();
+        final String rawStopWatchStart = MDC.get(LoggingField.STOP_WATCH_START.toString());
+
+        if (rawStopWatchStart == null)
+            throw new StopWatchNotStartedException();
+
+        final Long stopWatchStart = Long.valueOf(rawStopWatchStart);
+
+        MDC.remove(LoggingField.STOP_WATCH_START.toString());
+
+        final double elapsedTimeMillis = (stopWatchEnd - stopWatchStart) / 1000.0 / 1000.0;
+
+        LoggingContext.elapsedTime((long) elapsedTimeMillis, TimeUnit.MILLISECONDS);
+
+        return elapsedTimeMillis;
+    }
+
+    public static void put(String key, String value) {
+        MDC.put(key, value);
+    }
+
+    public static void clear() {
+        MDC.clear();
+    }
+
+    public static void remove(String key) {
+        MDC.remove(key);
+    }
+
+    public static void save() {
+        final JSONObject context = new JSONObject();
+
+        for (LoggingField field : LoggingField.values()) {
+            if (field == LoggingField.ELAPSED_TIME)
+                continue;
+
+            try {
+                context.put(field.toString(), MDC.get(field.toString()));
+            } catch (JSONException e) {
+                // Ignore - only occurs when the key is null (which can't happen)
+                // or the value is invalid (everything is converted to a string
+                // before it get put() to the MDC)
+            }
+        }
+
+        final String rawJsonArray = MDC.get(PREVIOUS_CONTEXTS_KEY);
+
+        if (rawJsonArray == null) {
+            final JSONArray stack = new JSONArray().put(context);
+
+            MDC.put(PREVIOUS_CONTEXTS_KEY, stack.toString());
+        } else {
+            try {
+                final JSONArray stack = new JSONArray(rawJsonArray).put(context);
+
+                MDC.put(PREVIOUS_CONTEXTS_KEY, stack.toString());
+            } catch (JSONException e) {
+                // Ignore
+            }
+        }
+    }
+
+    public static void restore() {
+
+        final String rawPreviousContexts = MDC.get(PREVIOUS_CONTEXTS_KEY);
+
+        if (rawPreviousContexts == null) {
+            throw new LoggingContextNotExistsException();
+        }
+
+        try {
+            final JSONArray previousContexts = new JSONArray(rawPreviousContexts);
+            final JSONObject previousContext = previousContexts.getJSONObject(previousContexts.length() - 1);
+
+            @SuppressWarnings("unchecked")
+            final Iterator<String> keys = previousContext.keys();
+            boolean foundElapsedTime = false;
+            while (keys.hasNext()) {
+                final String key = keys.next();
+                if (LoggingField.ELAPSED_TIME.toString().equals(key)) {
+                    foundElapsedTime = true;
+                }
+                try {
+                    MDC.put(key, previousContext.getString(key));
+                } catch (JSONException e) {
+                    // Ignore, only occurs when the key is null (cannot happen)
+                    // or the value is invalid (they are all strings)
+                }
+            }
+            if (!foundElapsedTime) {
+                MDC.remove(LoggingField.ELAPSED_TIME.toString());
+            }
+            MDC.put(PREVIOUS_CONTEXTS_KEY, removeLast(previousContexts).toString());
+        } catch (JSONException e) {
+            // Ignore, the previousContext is serialized from a JSONObject
+        }
+    }
+
+    public static void restoreIfPossible() {
+        try {
+            restore();
+        } catch (LoggingContextNotExistsException e) {
+            // Ignore
+        }
+    }
+
+    /**
+     * AJSC declares an ancient version of org.json:json in one of the parent POMs of this project.
+     * I tried to update our version of that library in our POM, but it's ignored because of the way
+     * AJSC has organized their <dependencies>. Had they put it into the <dependencyManagement> section,
+     * this method would not be necessary.
+     */
+    private static JSONArray removeLast(JSONArray previousContexts) {
+        final JSONArray result = new JSONArray();
+
+        for (int i = 0; i < previousContexts.length() - 1; i++) {
+            try {
+                result.put(previousContexts.getJSONObject(i));
+            } catch (JSONException e) {
+                // Ignore - not possible
+            }
+        }
+
+        return result;
+    }
+
+    public static Map<String, String> getCopy() {
+        final Map<String, String> copy = new HashMap<String, String>();
+
+        for (LoggingField field : LoggingField.values()) {
+            final String value = MDC.get(field.toString());
+
+            if (value != null)
+                copy.put(field.toString(), value);
+        }
+
+        return copy;
+    }
 }