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.slf4j;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Iterator;
022import java.util.concurrent.ConcurrentHashMap;
023import java.util.concurrent.ConcurrentMap;
024
025import org.apache.logging.log4j.Logger;
026import org.apache.logging.log4j.MarkerManager;
027import org.apache.logging.log4j.status.StatusLogger;
028import org.slf4j.IMarkerFactory;
029import org.slf4j.Marker;
030
031/**
032 * Log4j/SLF4J bridge to create SLF4J Markers based on name or based on existing SLF4J Markers.
033 */
034public class Log4jMarkerFactory implements IMarkerFactory {
035
036    private static final Logger LOGGER = StatusLogger.getLogger();
037
038    private final ConcurrentMap<String, Marker> markerMap = new ConcurrentHashMap<>();
039
040    /**
041     * Returns a Log4j Marker that is compatible with SLF4J.
042     * @param name The name of the Marker.
043     * @return A Marker.
044     */
045    @Override
046    public Marker getMarker(final String name) {
047        if (name == null) {
048            throw new IllegalArgumentException("Marker name must not be null");
049        }
050        final Marker marker = markerMap.get(name);
051        if (marker != null) {
052            return marker;
053        }
054        final org.apache.logging.log4j.Marker log4jMarker = MarkerManager.getMarker(name);
055        return addMarkerIfAbsent(name, log4jMarker);
056    }
057
058    private Marker addMarkerIfAbsent(final String name, final org.apache.logging.log4j.Marker log4jMarker) {
059        final Marker marker = new Log4jMarker(log4jMarker);
060        final Marker existing = markerMap.putIfAbsent(name, marker);
061        return existing == null ? marker : existing;
062    }
063
064    /**
065     * Returns a Log4j Marker converted from an existing custom SLF4J Marker.
066     * @param marker The SLF4J Marker to convert.
067     * @return A converted Log4j/SLF4J Marker.
068     * @since 2.1
069     */
070    public Marker getMarker(final Marker marker) {
071        if (marker == null) {
072            throw new IllegalArgumentException("Marker must not be null");
073        }
074        final Marker m = markerMap.get(marker.getName());
075        if (m != null) {
076            return m;
077        }
078        return addMarkerIfAbsent(marker.getName(), convertMarker(marker));
079    }
080
081    private static org.apache.logging.log4j.Marker convertMarker(final Marker original) {
082        if (original == null) {
083            throw new IllegalArgumentException("Marker must not be null");
084        }
085        return convertMarker(original, new ArrayList<Marker>());
086    }
087
088    private static org.apache.logging.log4j.Marker convertMarker(final Marker original,
089                                                                 final Collection<Marker> visited) {
090        final org.apache.logging.log4j.Marker marker = MarkerManager.getMarker(original.getName());
091        if (original.hasReferences()) {
092            final Iterator<Marker> it = original.iterator();
093            while (it.hasNext()) {
094                final Marker next = it.next();
095                if (visited.contains(next)) {
096                    LOGGER.warn("Found a cycle in Marker [{}]. Cycle will be broken.", next.getName());
097                } else {
098                    visited.add(next);
099                    marker.addParents(convertMarker(next, visited));
100                }
101            }
102        }
103        return marker;
104    }
105
106    /**
107     * Returns true if the Marker exists.
108     * @param name The Marker name.
109     * @return {@code true} if the Marker exists, {@code false} otherwise.
110     */
111    @Override
112    public boolean exists(final String name) {
113        return markerMap.containsKey(name);
114    }
115
116    /**
117     * Log4j does not support detached Markers. This method always returns false.
118     * @param name The Marker name.
119     * @return {@code false}
120     */
121    @Override
122    public boolean detachMarker(final String name) {
123        return false;
124    }
125
126    /**
127     * Log4j does not support detached Markers for performance reasons. The returned Marker is attached.
128     * @param name The Marker name.
129     * @return The named Marker (unmodified).
130     */
131    @Override
132    public Marker getDetachedMarker(final String name) {
133        LOGGER.warn("Log4j does not support detached Markers. Returned Marker [{}] will be unchanged.", name);
134        return getMarker(name);
135    }
136
137
138}