001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.impl; 018 019import java.lang.invoke.MethodHandle; 020import java.lang.invoke.MethodHandles; 021import java.lang.invoke.MethodType; 022import java.util.Map; 023import java.util.Map.Entry; 024 025import org.apache.logging.log4j.core.ContextDataInjector; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.util.Loader; 028import org.apache.logging.log4j.util.IndexedStringMap; 029import org.apache.logging.log4j.util.PropertiesUtil; 030import org.apache.logging.log4j.util.ReadOnlyStringMap; 031import org.apache.logging.log4j.util.SortedArrayStringMap; 032import org.apache.logging.log4j.util.StringMap; 033 034/** 035 * Factory for creating the StringMap instances used to initialize LogEvents' {@linkplain LogEvent#getContextData() 036 * context data}. When context data is {@linkplain ContextDataInjector injected} into the log event, these StringMap 037 * instances may be either populated with key-value pairs from the context, or completely replaced altogether. 038 * <p> 039 * By default returns {@code SortedArrayStringMap} objects. Can be configured by setting system property 040 * {@code "log4j2.ContextData"} to the fully qualified class name of a class implementing the {@code StringMap} 041 * interface. The class must have a public default constructor, and if possible should also have a public constructor 042 * that takes a single {@code int} argument for the initial capacity. 043 * </p> 044 * 045 * @see LogEvent#getContextData() 046 * @see ContextDataInjector 047 * @see SortedArrayStringMap 048 * @since 2.7 049 */ 050public class ContextDataFactory { 051 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 052 private static final String CLASS_NAME = PropertiesUtil.getProperties().getStringProperty("log4j2.ContextData"); 053 private static final Class<? extends StringMap> CACHED_CLASS = createCachedClass(CLASS_NAME); 054 private static final MethodHandle DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS); 055 private static final MethodHandle INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS); 056 057 private static final StringMap EMPTY_STRING_MAP = createContextData(0); 058 059 static { 060 EMPTY_STRING_MAP.freeze(); 061 } 062 063 private static Class<? extends StringMap> createCachedClass(final String className) { 064 if (className == null) { 065 return null; 066 } 067 try { 068 return Loader.loadClass(className).asSubclass(IndexedStringMap.class); 069 } catch (final Exception any) { 070 return null; 071 } 072 } 073 074 private static MethodHandle createDefaultConstructor(final Class<? extends StringMap> cachedClass) { 075 if (cachedClass == null) { 076 return null; 077 } 078 try { 079 return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class)); 080 } catch (final NoSuchMethodException | IllegalAccessException ignored) { 081 return null; 082 } 083 } 084 085 private static MethodHandle createInitialCapacityConstructor(final Class<? extends StringMap> cachedClass) { 086 if (cachedClass == null) { 087 return null; 088 } 089 try { 090 return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class, int.class)); 091 } catch (final NoSuchMethodException | IllegalAccessException ignored) { 092 return null; 093 } 094 } 095 096 public static StringMap createContextData() { 097 if (DEFAULT_CONSTRUCTOR == null) { 098 return new SortedArrayStringMap(); 099 } 100 try { 101 return (IndexedStringMap) DEFAULT_CONSTRUCTOR.invoke(); 102 } catch (final Throwable ignored) { 103 return new SortedArrayStringMap(); 104 } 105 } 106 107 public static StringMap createContextData(final int initialCapacity) { 108 if (INITIAL_CAPACITY_CONSTRUCTOR == null) { 109 return new SortedArrayStringMap(initialCapacity); 110 } 111 try { 112 return (IndexedStringMap) INITIAL_CAPACITY_CONSTRUCTOR.invoke(initialCapacity); 113 } catch (final Throwable ignored) { 114 return new SortedArrayStringMap(initialCapacity); 115 } 116 } 117 118 public static StringMap createContextData(final Map<String, String> context) { 119 final StringMap contextData = createContextData(context.size()); 120 for (final Entry<String, String> entry : context.entrySet()) { 121 contextData.putValue(entry.getKey(), entry.getValue()); 122 } 123 return contextData; 124 } 125 126 public static StringMap createContextData(final ReadOnlyStringMap readOnlyStringMap) { 127 final StringMap contextData = createContextData(readOnlyStringMap.size()); 128 contextData.putAll(readOnlyStringMap); 129 return contextData; 130 } 131 132 /** 133 * An empty pre-frozen IndexedStringMap. The returned object may be shared. 134 * 135 * @return an empty pre-frozen IndexedStringMap 136 */ 137 public static StringMap emptyFrozenContextData() { 138 return EMPTY_STRING_MAP; 139 } 140 141}