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 */
017
018package org.apache.logging.log4j.core.net;
019
020import java.net.URI;
021import java.net.URISyntaxException;
022import java.util.Properties;
023import java.util.concurrent.TimeUnit;
024
025import javax.naming.Context;
026import javax.naming.InitialContext;
027import javax.naming.NamingException;
028
029import org.apache.logging.log4j.core.appender.AbstractManager;
030import org.apache.logging.log4j.core.appender.ManagerFactory;
031import org.apache.logging.log4j.core.util.JndiCloser;
032import org.apache.logging.log4j.util.PropertiesUtil;
033
034/**
035 * Manages a JNDI {@link javax.naming.Context}.
036 *
037 * @since 2.1
038 */
039public class JndiManager extends AbstractManager {
040
041    private static final JndiManagerFactory FACTORY = new JndiManagerFactory();
042    private static final String PREFIX = "log4j2.enableJndi";
043    private static final String JAVA_SCHEME = "java";
044
045    private static final boolean JNDI_CONTEXT_SELECTOR_ENABLED = isJndiEnabled("ContextSelector");
046    private static final boolean JNDI_JDBC_ENABLED = isJndiEnabled("Jdbc");
047    private static final boolean JNDI_JMS_ENABLED = isJndiEnabled("Jms");
048    private static final boolean JNDI_LOOKUP_ENABLED = isJndiEnabled("Lookup");
049
050    private final InitialContext context;
051
052    private static boolean isJndiEnabled(final String subKey) {
053        return PropertiesUtil.getProperties().getBooleanProperty(PREFIX + subKey, false);
054    }
055
056    public static boolean isJndiEnabled() {
057        return isJndiContextSelectorEnabled() || isJndiJdbcEnabled() || isJndiJmsEnabled() || isJndiLookupEnabled();
058    }
059
060    public static boolean isJndiContextSelectorEnabled() {
061        return JNDI_CONTEXT_SELECTOR_ENABLED;
062    }
063
064    public static boolean isJndiJdbcEnabled() {
065        return JNDI_JDBC_ENABLED;
066    }
067
068    public static boolean isJndiJmsEnabled() {
069        return JNDI_JMS_ENABLED;
070    }
071
072    public static boolean isJndiLookupEnabled() {
073        return JNDI_LOOKUP_ENABLED;
074    }
075
076    private JndiManager(final String name, final InitialContext context) {
077        super(null, name);
078        this.context = context;
079    }
080
081    /**
082     * Gets the default JndiManager using the default {@link javax.naming.InitialContext}.
083     *
084     * @return the default JndiManager
085     */
086    public static JndiManager getDefaultManager() {
087        return getManager(JndiManager.class.getName(), FACTORY, null);
088    }
089
090    /**
091     * Gets a named JndiManager using the default {@link javax.naming.InitialContext}.
092     *
093     * @param name the name of the JndiManager instance to create or use if available
094     * @return a default JndiManager
095     */
096    public static JndiManager getDefaultManager(final String name) {
097        return getManager(name, FACTORY, null);
098    }
099
100    /**
101     * Gets a JndiManager with the provided configuration information.
102     *
103     * @param initialContextFactoryName Fully qualified class name of an implementation of
104     *                                  {@link javax.naming.spi.InitialContextFactory}.
105     * @param providerURL               The provider URL to use for the JNDI connection (specific to the above factory).
106     * @param urlPkgPrefixes            A colon-separated list of package prefixes for the class name of the factory
107     *                                  class that will create a URL context factory
108     * @param securityPrincipal         The name of the identity of the Principal.
109     * @param securityCredentials       The security credentials of the Principal.
110     * @param additionalProperties      Any additional JNDI environment properties to set or {@code null} for none.
111     * @return the JndiManager for the provided parameters.
112     */
113    public static JndiManager getJndiManager(final String initialContextFactoryName,
114            final String providerURL,
115            final String urlPkgPrefixes,
116            final String securityPrincipal,
117            final String securityCredentials,
118            final Properties additionalProperties) {
119        final Properties properties = createProperties(initialContextFactoryName, providerURL, urlPkgPrefixes,
120                securityPrincipal, securityCredentials, additionalProperties);
121        return getManager(createManagerName(), FACTORY, properties);
122    }
123
124    /**
125     * Gets a JndiManager with the provided configuration information.
126     *
127     * @param properties JNDI properties, usually created by calling {@link #createProperties(String, String, String, String, String, Properties)}.
128     * @return the JndiManager for the provided parameters.
129     * @see #createProperties(String, String, String, String, String, Properties)
130     * @since 2.9
131     */
132    public static JndiManager getJndiManager(final Properties properties) {
133        return getManager(createManagerName(), FACTORY, properties);
134    }
135
136    private static String createManagerName() {
137        return JndiManager.class.getName() + '@' + JndiManager.class.hashCode();
138    }
139
140    /**
141     * Creates JNDI Properties with the provided configuration information.
142     *
143     * @param initialContextFactoryName
144     *            Fully qualified class name of an implementation of {@link javax.naming.spi.InitialContextFactory}.
145     * @param providerURL
146     *            The provider URL to use for the JNDI connection (specific to the above factory).
147     * @param urlPkgPrefixes
148     *            A colon-separated list of package prefixes for the class name of the factory class that will create a
149     *            URL context factory
150     * @param securityPrincipal
151     *            The name of the identity of the Principal.
152     * @param securityCredentials
153     *            The security credentials of the Principal.
154     * @param additionalProperties
155     *            Any additional JNDI environment properties to set or {@code null} for none.
156     * @return the Properties for the provided parameters.
157     * @since 2.9
158     */
159    public static Properties createProperties(final String initialContextFactoryName, final String providerURL,
160            final String urlPkgPrefixes, final String securityPrincipal, final String securityCredentials,
161            final Properties additionalProperties) {
162        if (initialContextFactoryName == null) {
163            return null;
164        }
165        final Properties properties = new Properties();
166        properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
167        if (providerURL != null) {
168            properties.setProperty(Context.PROVIDER_URL, providerURL);
169        } else {
170            LOGGER.warn("The JNDI InitialContextFactory class name [{}] was provided, but there was no associated "
171                    + "provider URL. This is likely to cause problems.", initialContextFactoryName);
172        }
173        if (urlPkgPrefixes != null) {
174            properties.setProperty(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
175        }
176        if (securityPrincipal != null) {
177            properties.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal);
178            if (securityCredentials != null) {
179                properties.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials);
180            } else {
181                LOGGER.warn("A security principal [{}] was provided, but with no corresponding security credentials.",
182                        securityPrincipal);
183            }
184        }
185        if (additionalProperties != null) {
186            properties.putAll(additionalProperties);
187        }
188        return properties;
189    }
190
191    @Override
192    protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
193        return JndiCloser.closeSilently(this.context);
194    }
195
196    /**
197     * Looks up a named object through this JNDI context.
198     *
199     * @param name name of the object to look up.
200     * @param <T>  the type of the object.
201     * @return the named object if it could be located.
202     * @throws  NamingException if a naming exception is encountered
203     */
204    @SuppressWarnings("unchecked")
205    public <T> T lookup(final String name) throws NamingException {
206        if (context == null) {
207            return null;
208        }
209        try {
210            URI uri = new URI(name);
211            if (uri.getScheme() == null || uri.getScheme().equals(JAVA_SCHEME)) {
212                return (T) this.context.lookup(name);
213            }
214            LOGGER.warn("Unsupported JNDI URI - {}", name);
215        } catch (URISyntaxException ex) {
216            LOGGER.warn("Invalid JNDI URI - {}", name);
217        }
218        return null;
219    }
220
221    private static class JndiManagerFactory implements ManagerFactory<JndiManager, Properties> {
222
223        @Override
224        public JndiManager createManager(final String name, final Properties data) {
225            if (!isJndiEnabled()) {
226                throw new IllegalStateException(String.format("JNDI must be enabled by setting one of the %s* properties to true", PREFIX));
227            }
228            try {
229                return new JndiManager(name, new InitialContext(data));
230            } catch (final NamingException e) {
231                LOGGER.error("Error creating JNDI InitialContext for '{}'.", name, e);
232                return null;
233            }
234        }
235
236    }
237
238    @Override
239    public String toString() {
240        return "JndiManager [context=" + context + ", count=" + count + "]";
241    }
242
243}