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.net;
018
019import java.io.Serializable;
020import javax.jms.JMSException;
021import javax.jms.Session;
022import javax.jms.Topic;
023import javax.jms.TopicConnection;
024import javax.jms.TopicConnectionFactory;
025import javax.jms.TopicPublisher;
026import javax.jms.TopicSession;
027import javax.naming.Context;
028import javax.naming.NamingException;
029
030import org.apache.logging.log4j.core.appender.ManagerFactory;
031
032/**
033 * Manager for JMS Topic connections.
034 */
035public class JMSTopicManager extends AbstractJMSManager {
036
037    private static final JMSTopicManagerFactory FACTORY = new JMSTopicManagerFactory();
038
039    private TopicInfo info;
040    private final String factoryBindingName;
041    private final String topicBindingName;
042    private final String userName;
043    private final String password;
044    private final Context context;
045    /**
046     * Constructor.
047     * @param name The unique name of the connection.
048     * @param context The context.
049     * @param factoryBindingName The factory binding name.
050     * @param topicBindingName The queue binding name.
051     * @param userName The user name.
052     * @param password The credentials for the user.
053     * @param info The Queue connection info.
054     */
055    protected JMSTopicManager(final String name, final Context context, final String factoryBindingName,
056                              final String topicBindingName, final String userName, final String password,
057                              final TopicInfo info) {
058        super(name);
059        this.context = context;
060        this.factoryBindingName = factoryBindingName;
061        this.topicBindingName = topicBindingName;
062        this.userName = userName;
063        this.password = password;
064        this.info = info;
065    }
066
067    /**
068     * Obtain a JSMTopicManager.
069     * @param factoryName The fully qualified class name of the InitialContextFactory.
070     * @param providerURL The URL of the provider to use.
071     * @param urlPkgPrefixes A colon-separated list of package prefixes for the class name of the factory class that
072     * will create a URL context factory
073     * @param securityPrincipalName The name of the identity of the Principal.
074     * @param securityCredentials The security credentials of the Principal.
075     * @param factoryBindingName The name to locate in the Context that provides the TopicConnectionFactory.
076     * @param topicBindingName The name to use to locate the Topic.
077     * @param userName The userid to use to create the Topic Connection.
078     * @param password The password to use to create the Topic Connection.
079     * @return A JMSTopicManager.
080     */
081    public static JMSTopicManager getJMSTopicManager(final String factoryName, final String providerURL,
082                                                     final String urlPkgPrefixes, final String securityPrincipalName,
083                                                     final String securityCredentials, final String factoryBindingName,
084                                                     final String topicBindingName, final String userName,
085                                                     final String password) {
086
087        if (factoryBindingName == null) {
088            LOGGER.error("No factory name provided for JMSTopicManager");
089            return null;
090        }
091        if (topicBindingName == null) {
092            LOGGER.error("No topic name provided for JMSTopicManager");
093            return null;
094        }
095
096        final String name = "JMSTopic:" + factoryBindingName + '.' + topicBindingName;
097        return getManager(name, FACTORY, new FactoryData(factoryName, providerURL, urlPkgPrefixes,
098            securityPrincipalName, securityCredentials, factoryBindingName, topicBindingName, userName, password));
099    }
100
101
102    @Override
103    public void send(final Serializable object) throws Exception {
104        if (info == null) {
105            info = connect(context, factoryBindingName, topicBindingName, userName, password, false);
106        }
107        try {
108            super.send(object, info.session, info.publisher);
109        } catch (final Exception ex) {
110            cleanup(true);
111            throw ex;
112        }
113    }
114
115    @Override
116    public void releaseSub() {
117        if (info != null) {
118            cleanup(false);
119        }
120    }
121
122    private void cleanup(final boolean quiet) {
123        try {
124            info.session.close();
125        } catch (final Exception e) {
126            if (!quiet) {
127                LOGGER.error("Error closing session for " + getName(), e);
128            }
129        }
130        try {
131            info.conn.close();
132        } catch (final Exception e) {
133            if (!quiet) {
134                LOGGER.error("Error closing connection for " + getName(), e);
135            }
136        }
137        info = null;
138    }
139
140    /**
141     * Data for the factory.
142     */
143    private static class FactoryData {
144        private final String factoryName;
145        private final String providerURL;
146        private final String urlPkgPrefixes;
147        private final String securityPrincipalName;
148        private final String securityCredentials;
149        private final String factoryBindingName;
150        private final String topicBindingName;
151        private final String userName;
152        private final String password;
153
154        public FactoryData(final String factoryName, final String providerURL, final String urlPkgPrefixes,
155                           final String securityPrincipalName, final String securityCredentials,
156                           final String factoryBindingName, final String topicBindingName,
157                           final String userName, final String password) {
158            this.factoryName = factoryName;
159            this.providerURL = providerURL;
160            this.urlPkgPrefixes = urlPkgPrefixes;
161            this.securityPrincipalName = securityPrincipalName;
162            this.securityCredentials = securityCredentials;
163            this.factoryBindingName = factoryBindingName;
164            this.topicBindingName = topicBindingName;
165            this.userName = userName;
166            this.password = password;
167        }
168    }
169
170    private static TopicInfo connect(final Context context, final String factoryBindingName,
171                                     final String queueBindingName, final String userName, final String password,
172                                     final boolean suppress) throws Exception {
173        try {
174            final TopicConnectionFactory factory = (TopicConnectionFactory) lookup(context, factoryBindingName);
175            TopicConnection conn;
176            if (userName != null) {
177                conn = factory.createTopicConnection(userName, password);
178            } else {
179                conn = factory.createTopicConnection();
180            }
181            final TopicSession sess = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
182            final Topic topic = (Topic) lookup(context, queueBindingName);
183            final TopicPublisher publisher = sess.createPublisher(topic);
184            conn.start();
185            return new TopicInfo(conn, sess, publisher);
186        } catch (final NamingException ex) {
187            LOGGER.warn("Unable to locate connection factory " + factoryBindingName, ex);
188            if (!suppress) {
189                throw ex;
190            }
191        } catch (final JMSException ex) {
192            LOGGER.warn("Unable to create connection to queue " + queueBindingName, ex);
193            if (!suppress) {
194                throw ex;
195            }
196        }
197        return null;
198    }
199
200    /** Topic connection information */
201    private static class TopicInfo {
202        private final TopicConnection conn;
203        private final TopicSession session;
204        private final TopicPublisher publisher;
205
206        public TopicInfo(final TopicConnection conn, final TopicSession session, final TopicPublisher publisher) {
207            this.conn = conn;
208            this.session = session;
209            this.publisher = publisher;
210        }
211    }
212
213    /**
214     * Factory to create the JMSQueueManager.
215     */
216    private static class JMSTopicManagerFactory implements ManagerFactory<JMSTopicManager, FactoryData> {
217
218        @Override
219        public JMSTopicManager createManager(final String name, final FactoryData data) {
220            try {
221                final Context ctx = createContext(data.factoryName, data.providerURL, data.urlPkgPrefixes,
222                    data.securityPrincipalName, data.securityCredentials);
223                final TopicInfo info = connect(ctx, data.factoryBindingName, data.topicBindingName, data.userName,
224                    data.password, true);
225                return new JMSTopicManager(name, ctx, data.factoryBindingName, data.topicBindingName,
226                    data.userName, data.password, info);
227            } catch (final NamingException ex) {
228                LOGGER.error("Unable to locate resource", ex);
229            } catch (final Exception ex) {
230                LOGGER.error("Unable to connect", ex);
231            }
232
233            return null;
234        }
235    }
236}