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.selector;
018
019import java.net.URI;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.ConcurrentMap;
026import javax.naming.Context;
027import javax.naming.InitialContext;
028import javax.naming.NameNotFoundException;
029import javax.naming.NamingException;
030
031import org.apache.logging.log4j.core.LoggerContext;
032import org.apache.logging.log4j.core.helpers.Constants;
033import org.apache.logging.log4j.core.impl.ContextAnchor;
034import org.apache.logging.log4j.status.StatusLogger;
035
036/**
037 * This class can be used to define a
038 * custom logger repository.  It makes use of the fact that in J2EE
039 * environments, each web-application is guaranteed to have its own JNDI
040 * context relative to the <code>java:comp/env</code> context. In EJBs, each
041 * enterprise bean (albeit not each application) has its own context relative
042 * to the <code>java:comp/env</code> context.  An <code>env-entry</code> in a
043 * deployment descriptor provides the information to the JNDI context.  Once the
044 * <code>env-entry</code> is set, a repository selector can query the JNDI
045 * application context to look up the value of the entry. The logging context of
046 * the web-application will depend on the value the env-entry.  The JNDI context
047 *  which is looked up by this class is
048 * <code>java:comp/env/log4j/context-name</code>.
049 *
050 * <p>Here is an example of an <code>env-entry<code>:
051 * <blockquote>
052 * <pre>
053 * &lt;env-entry&gt;
054 *   &lt;description&gt;JNDI logging context name for this app&lt;/description&gt;
055 *   &lt;env-entry-name&gt;log4j/context-name&lt;/env-entry-name&gt;
056 *   &lt;env-entry-value&gt;aDistinctiveLoggingContextName&lt;/env-entry-value&gt;
057 *   &lt;env-entry-type&gt;java.lang.String&lt;/env-entry-type&gt;
058 * &lt;/env-entry&gt;
059 * </pre>
060 * </blockquote>
061 * </p>
062 *
063 * <p><em>If multiple applications use the same logging context name, then they
064 * will share the same logging context.</em>
065 * </p>
066 *
067 *<p>You can also specify the URL for this context's configuration resource.
068 * This repository selector (ContextJNDISelector) will use this resource
069 * to automatically configure the log4j repository.
070 *</p>
071 ** <blockquote>
072 * <pre>
073 * &lt;env-entry&gt;
074 *   &lt;description&gt;URL for configuring log4j context&lt;/description&gt;
075 *   &lt;env-entry-name&gt;log4j/configuration-resource&lt;/env-entry-name&gt;
076 *   &lt;env-entry-value&gt;urlOfConfigrationResource&lt;/env-entry-value&gt;
077 *   &lt;env-entry-type&gt;java.lang.String&lt;/env-entry-type&gt;
078 * &lt;/env-entry&gt;
079 * </pre>
080 * </blockquote>
081 *
082 * <p>It usually good practice for configuration resources of distinct
083 * applications to have distinct names. However, if this is not possible
084 * Naming
085 * </p>
086 *
087 */
088public class JNDIContextSelector implements NamedContextSelector {
089
090    private static final LoggerContext CONTEXT = new LoggerContext("Default");
091
092    private static final ConcurrentMap<String, LoggerContext> CONTEXT_MAP =
093        new ConcurrentHashMap<String, LoggerContext>();
094
095    private static final StatusLogger LOGGER = StatusLogger.getLogger();
096
097    @Override
098    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
099        return getContext(fqcn, loader, currentContext, null);
100    }
101
102    @Override
103    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
104                                    final URI configLocation) {
105
106        final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
107        if (lc != null) {
108            return lc;
109        }
110
111        String loggingContextName = null;
112
113        try {
114            final Context ctx = new InitialContext();
115            loggingContextName = (String) lookup(ctx, Constants.JNDI_CONTEXT_NAME);
116        } catch (final NamingException ne) {
117            LOGGER.error("Unable to lookup " + Constants.JNDI_CONTEXT_NAME, ne);
118        }
119
120        return loggingContextName == null ? CONTEXT : locateContext(loggingContextName, null, configLocation);
121    }
122
123    @Override
124    public LoggerContext locateContext(final String name, final Object externalContext, final URI configLocation) {
125        if (name == null) {
126            LOGGER.error("A context name is required to locate a LoggerContext");
127            return null;
128        }
129        if (!CONTEXT_MAP.containsKey(name)) {
130            final LoggerContext ctx = new LoggerContext(name, externalContext, configLocation);
131            CONTEXT_MAP.putIfAbsent(name, ctx);
132        }
133        return CONTEXT_MAP.get(name);
134    }
135
136    @Override
137    public void removeContext(final LoggerContext context) {
138
139        for (final Map.Entry<String, LoggerContext> entry : CONTEXT_MAP.entrySet()) {
140            if (entry.getValue().equals(context)) {
141                CONTEXT_MAP.remove(entry.getKey());
142            }
143        }
144    }
145
146    @Override
147    public LoggerContext removeContext(final String name) {
148        return CONTEXT_MAP.remove(name);
149    }
150
151    @Override
152    public List<LoggerContext> getLoggerContexts() {
153        final List<LoggerContext> list = new ArrayList<LoggerContext>(CONTEXT_MAP.values());
154        return Collections.unmodifiableList(list);
155    }
156
157
158    protected static Object lookup(final Context ctx, final String name) throws NamingException {
159        if (ctx == null) {
160            return null;
161        }
162        try {
163            return ctx.lookup(name);
164        } catch (final NameNotFoundException e) {
165            LOGGER.error("Could not find name [" + name + "].");
166            throw e;
167        }
168    }
169}