View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.lang.invoke.MethodHandle;
20  import java.lang.invoke.MethodHandles;
21  import java.lang.invoke.MethodType;
22  
23  import org.apache.logging.log4j.core.ContextDataInjector;
24  import org.apache.logging.log4j.core.LogEvent;
25  import org.apache.logging.log4j.util.LoaderUtil;
26  import org.apache.logging.log4j.util.PropertiesUtil;
27  import org.apache.logging.log4j.util.SortedArrayStringMap;
28  import org.apache.logging.log4j.util.StringMap;
29  
30  /**
31   * Factory for creating the StringMap instances used to initialize LogEvents'
32   * {@linkplain LogEvent#getContextData() context data}. When context data is
33   * {@linkplain ContextDataInjector injected} into the log event, these StringMap
34   * instances may be either populated with key-value pairs from the context, or completely replaced altogether.
35   * <p>
36   * By default returns {@code SortedArrayStringMap} objects. Can be configured by setting system property
37   * {@code "log4j2.ContextData"} to the fully qualified class name of a class implementing the
38   * {@code StringMap} interface. The class must have a public default constructor, and if possible should also have a
39   * public constructor that takes a single {@code int} argument for the initial capacity.
40   * </p>
41   *
42   * @see LogEvent#getContextData()
43   * @see ContextDataInjector
44   * @see SortedArrayStringMap
45   * @since 2.7
46   */
47  public class ContextDataFactory {
48      private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
49      private static final String CLASS_NAME = PropertiesUtil.getProperties().getStringProperty("log4j2.ContextData");
50      private static final Class<? extends StringMap> CACHED_CLASS = createCachedClass(CLASS_NAME);
51      private static final MethodHandle DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS);
52      private static final MethodHandle INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS);
53  
54      private static final StringMap EMPTY_STRING_MAP = createContextData(1);
55      static {
56          EMPTY_STRING_MAP.freeze();
57      }
58  
59      private static Class<? extends StringMap> createCachedClass(final String className) {
60          if (className == null) {
61              return null;
62          }
63          try {
64              return LoaderUtil.loadClass(className).asSubclass(StringMap.class);
65          } catch (final Exception any) {
66              return null;
67          }
68      }
69  
70      private static MethodHandle createDefaultConstructor(final Class<? extends StringMap> cachedClass) {
71          if (cachedClass == null) {
72              return null;
73          }
74          try {
75              return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class));
76          } catch (final NoSuchMethodException | IllegalAccessException ignored) {
77              return null;
78          }
79      }
80  
81      private static MethodHandle createInitialCapacityConstructor(final Class<? extends StringMap> cachedClass) {
82          if (cachedClass == null) {
83              return null;
84          }
85          try {
86              return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class, int.class));
87          } catch (final NoSuchMethodException | IllegalAccessException ignored) {
88              return null;
89          }
90      }
91  
92      public static StringMap createContextData() {
93          if (DEFAULT_CONSTRUCTOR == null) {
94              return new SortedArrayStringMap();
95          }
96          try {
97              return (StringMap) DEFAULT_CONSTRUCTOR.invoke();
98          } catch (final Throwable ignored) {
99              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 }