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}