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; 022 023import org.apache.logging.log4j.core.ContextDataInjector; 024import org.apache.logging.log4j.core.LogEvent; 025import org.apache.logging.log4j.util.LoaderUtil; 026import org.apache.logging.log4j.util.PropertiesUtil; 027import org.apache.logging.log4j.util.SortedArrayStringMap; 028import org.apache.logging.log4j.util.StringMap; 029 030/** 031 * Factory for creating the StringMap instances used to initialize LogEvents' 032 * {@linkplain LogEvent#getContextData() context data}. When context data is 033 * {@linkplain ContextDataInjector injected} into the log event, these StringMap 034 * instances may be either populated with key-value pairs from the context, or completely replaced altogether. 035 * <p> 036 * By default returns {@code SortedArrayStringMap} objects. Can be configured by setting system property 037 * {@code "log4j2.ContextData"} to the fully qualified class name of a class implementing the 038 * {@code StringMap} interface. The class must have a public default constructor, and if possible should also have a 039 * public constructor that takes a single {@code int} argument for the initial capacity. 040 * </p> 041 * 042 * @see LogEvent#getContextData() 043 * @see ContextDataInjector 044 * @see SortedArrayStringMap 045 * @since 2.7 046 */ 047public class ContextDataFactory { 048 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 049 private static final String CLASS_NAME = PropertiesUtil.getProperties().getStringProperty("log4j2.ContextData"); 050 private static final Class<? extends StringMap> CACHED_CLASS = createCachedClass(CLASS_NAME); 051 private static final MethodHandle DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS); 052 private static final MethodHandle INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS); 053 054 private static final StringMap EMPTY_STRING_MAP = createContextData(1); 055 static { 056 EMPTY_STRING_MAP.freeze(); 057 } 058 059 private static Class<? extends StringMap> createCachedClass(final String className) { 060 if (className == null) { 061 return null; 062 } 063 try { 064 return LoaderUtil.loadClass(className).asSubclass(StringMap.class); 065 } catch (final Exception any) { 066 return null; 067 } 068 } 069 070 private static MethodHandle createDefaultConstructor(final Class<? extends StringMap> cachedClass) { 071 if (cachedClass == null) { 072 return null; 073 } 074 try { 075 return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class)); 076 } catch (final NoSuchMethodException | IllegalAccessException ignored) { 077 return null; 078 } 079 } 080 081 private static MethodHandle createInitialCapacityConstructor(final Class<? extends StringMap> cachedClass) { 082 if (cachedClass == null) { 083 return null; 084 } 085 try { 086 return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class, int.class)); 087 } catch (final NoSuchMethodException | IllegalAccessException ignored) { 088 return null; 089 } 090 } 091 092 public static StringMap createContextData() { 093 if (DEFAULT_CONSTRUCTOR == null) { 094 return new SortedArrayStringMap(); 095 } 096 try { 097 return (StringMap) DEFAULT_CONSTRUCTOR.invoke(); 098 } catch (final Throwable ignored) { 099 return new SortedArrayStringMap(); 100 } 101 } 102 103 public static StringMap createContextData(final int initialCapacity) { 104 if (INITIAL_CAPACITY_CONSTRUCTOR == null) { 105 return new SortedArrayStringMap(initialCapacity); 106 } 107 try { 108 return (StringMap) INITIAL_CAPACITY_CONSTRUCTOR.invoke(initialCapacity); 109 } catch (final Throwable ignored) { 110 return new SortedArrayStringMap(initialCapacity); 111 } 112 } 113 114 /** 115 * An empty pre-frozen StringMap. The returned object may be shared. 116 * 117 * @return an empty pre-frozen StringMap 118 */ 119 public static StringMap emptyFrozenContextData() { 120 return EMPTY_STRING_MAP; 121 } 122}