View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.audit.handlers.jms;
18  
19  import jakarta.jms.ConnectionFactory;
20  import jakarta.jms.Topic;
21  import java.util.Hashtable;
22  import javax.naming.InitialContext;
23  import javax.naming.NamingException;
24  import org.forgerock.audit.handlers.jms.JmsAuditEventHandlerConfiguration.JndiConfiguration;
25  import org.forgerock.json.resource.InternalServerErrorException;
26  import org.forgerock.json.resource.ResourceException;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  /**
31   * Uses Jndi to get the JMS {@link Topic topic} and {@link ConnectionFactory connection factory}.
32   */
33  class JndiJmsContextManager implements JmsContextManager {
34  
35      private static final Logger logger = LoggerFactory.getLogger(JndiJmsContextManager.class);
36  
37      private Topic topic;
38      private ConnectionFactory connectionFactory;
39      private final InitialContext context;
40      private final JndiConfiguration jndiConfiguration;
41  
42      /**
43       * Given the configuration, this builds a JMS InitialContext. The classloader of this class will be used as
44       * the context classloader {@link Thread#setContextClassLoader(ClassLoader)}.
45       *
46       * @param configuration The {@link JndiConfiguration JNDI configuration}.
47       * @throws InternalServerErrorException If unable to create the {@link InitialContext JNDI context}
48       */
49      JndiJmsContextManager(JndiConfiguration configuration) throws ResourceException {
50          ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
51          try {
52              Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
53              jndiConfiguration = configuration;
54              context = new InitialContext(new Hashtable<>(configuration.getContextProperties()));
55          } catch (NamingException e) {
56              throw new InternalServerErrorException("Encountered issue building initial JNDI context", e);
57          } finally {
58              Thread.currentThread().setContextClassLoader(originalContextClassLoader);
59          }
60      }
61  
62      /**
63       * Returns the {@link Topic JMS topic} to use for JMS publish/subscribe functionality.
64       *
65       * @return The {@link Topic JMS topic} to use for JMS publish/subscribe functionality.
66       * @throws InternalServerErrorException If unable to retrieve the {@link Topic JMS topic}.
67       */
68      @Override
69      public Topic getTopic() throws InternalServerErrorException {
70          try {
71              if (topic == null) {
72                  topic = getObject(jndiConfiguration.getTopicName(), Topic.class);
73              }
74              return topic;
75          } catch (NamingException e) {
76              throw new InternalServerErrorException(e.getMessage(), e);
77          }
78      }
79  
80      /**
81       * Returns the {@link ConnectionFactory JMS connection factory} to use to connect to JMS services.
82       * @return the {@link ConnectionFactory JMS connection factory} to use to connect to JMS services.
83       * @throws InternalServerErrorException If unable to retrieve the {@link ConnectionFactory JMS connection factory}.
84       */
85      @Override
86      public ConnectionFactory getConnectionFactory() throws InternalServerErrorException {
87          try {
88              if (connectionFactory == null) {
89                  connectionFactory = getObject(jndiConfiguration.getConnectionFactoryName(), ConnectionFactory.class);
90              }
91              return connectionFactory;
92          } catch (NamingException e) {
93              throw new InternalServerErrorException(e.getMessage(), e);
94          }
95      }
96  
97      private <T> T getObject(final String jndiName, final Class<T> clazz)
98              throws NamingException, InternalServerErrorException {
99  
100         ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
101         try {
102             Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
103             final Object object = context.lookup(jndiName);
104             if (clazz.isInstance(object)) {
105                 return (T) object;
106             }
107             final String error;
108             if (null == object) {
109                 error = String.format("No Object was found at JNDI name '%s'", jndiName);
110             } else {
111                 error = String.format("JNDI lookup('%s') did not return a '%s'. It returned a '%s'='%s'",
112                         jndiName, clazz.getCanonicalName(), object.getClass().getCanonicalName(), object.toString());
113             }
114             logger.error(error);
115             throw new InternalServerErrorException(error);
116         } finally {
117             Thread.currentThread().setContextClassLoader(originalContextClassLoader);
118         }
119     }
120 }