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.appender.mom;
019
020import java.io.Serializable;
021import java.util.Properties;
022import java.util.concurrent.TimeUnit;
023
024import javax.jms.JMSException;
025
026import org.apache.logging.log4j.core.Appender;
027import org.apache.logging.log4j.core.Filter;
028import org.apache.logging.log4j.core.Layout;
029import org.apache.logging.log4j.core.LogEvent;
030import org.apache.logging.log4j.core.appender.AbstractAppender;
031import org.apache.logging.log4j.core.appender.AbstractManager;
032import org.apache.logging.log4j.core.appender.mom.JmsManager.JmsManagerConfiguration;
033import org.apache.logging.log4j.core.config.Node;
034import org.apache.logging.log4j.core.config.plugins.Plugin;
035import org.apache.logging.log4j.core.config.plugins.PluginAliases;
036import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
037import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
038import org.apache.logging.log4j.core.config.plugins.PluginElement;
039import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
040import org.apache.logging.log4j.core.net.JndiManager;
041
042/**
043 * Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However,
044 * configurations set up for the 2.0 version of the JMS appenders will still work.
045 */
046@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
047@PluginAliases({ "JMSQueue", "JMSTopic" })
048public class JmsAppender extends AbstractAppender {
049
050    public static class Builder implements org.apache.logging.log4j.core.util.Builder<JmsAppender> {
051
052        public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000;
053
054        @PluginBuilderAttribute
055        @Required(message = "A name for the JmsAppender must be specified")
056        private String name;
057
058        @PluginBuilderAttribute
059        private String factoryName;
060
061        @PluginBuilderAttribute
062        private String providerUrl;
063
064        @PluginBuilderAttribute
065        private String urlPkgPrefixes;
066
067        @PluginBuilderAttribute
068        private String securityPrincipalName;
069
070        @PluginBuilderAttribute(sensitive = true)
071        private String securityCredentials;
072
073        @PluginBuilderAttribute
074        @Required(message = "A javax.jms.ConnectionFactory JNDI name must be specified")
075        private String factoryBindingName;
076
077        @PluginBuilderAttribute
078        @PluginAliases({ "queueBindingName", "topicBindingName" })
079        @Required(message = "A javax.jms.Destination JNDI name must be specified")
080        private String destinationBindingName;
081
082        @PluginBuilderAttribute
083        private String userName;
084
085        @PluginBuilderAttribute(sensitive = true)
086        private char[] password;
087
088        @PluginElement("Layout")
089        private Layout<? extends Serializable> layout;
090
091        @PluginElement("Filter")
092        private Filter filter;
093
094        private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS;
095
096        @PluginBuilderAttribute
097        private boolean ignoreExceptions = true;
098
099        @PluginBuilderAttribute
100        private boolean immediateFail;
101
102        // Programmatic access only for now.
103        private JmsManager jmsManager;
104
105        private Builder() {
106        }
107
108        @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender
109        @Override
110        public JmsAppender build() {
111            JmsManager actualJmsManager = jmsManager;
112            JmsManagerConfiguration configuration = null;
113            if (actualJmsManager == null) {
114                final Properties jndiProperties = JndiManager.createProperties(factoryName, providerUrl, urlPkgPrefixes,
115                        securityPrincipalName, securityCredentials, null);
116                configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName,
117                        userName, password, false, reconnectIntervalMillis);
118                actualJmsManager = AbstractManager.getManager(name, JmsManager.FACTORY, configuration);
119            }
120            if (actualJmsManager == null) {
121                // JmsManagerFactory has already logged an ERROR.
122                return null;
123            }
124            if (layout == null) {
125                LOGGER.error("No layout provided for JmsAppender");
126                return null;
127            }
128            try {
129                return new JmsAppender(name, filter, layout, ignoreExceptions, actualJmsManager);
130            } catch (final JMSException e) {
131                //  Never happens since the ctor no longer actually throws a JMSException.
132                throw new IllegalStateException(e);
133            }
134        }
135
136        public Builder setDestinationBindingName(final String destinationBindingName) {
137            this.destinationBindingName = destinationBindingName;
138            return this;
139        }
140
141        public Builder setFactoryBindingName(final String factoryBindingName) {
142            this.factoryBindingName = factoryBindingName;
143            return this;
144        }
145
146        public Builder setFactoryName(final String factoryName) {
147            this.factoryName = factoryName;
148            return this;
149        }
150
151        public Builder setFilter(final Filter filter) {
152            this.filter = filter;
153            return this;
154        }
155
156        public Builder setIgnoreExceptions(final boolean ignoreExceptions) {
157            this.ignoreExceptions = ignoreExceptions;
158            return this;
159        }
160
161        public Builder setImmediateFail(final boolean immediateFail) {
162            this.immediateFail = immediateFail;
163            return this;
164        }
165
166        public Builder setJmsManager(final JmsManager jmsManager) {
167            this.jmsManager = jmsManager;
168            return this;
169        }
170
171        public Builder setLayout(final Layout<? extends Serializable> layout) {
172            this.layout = layout;
173            return this;
174        }
175
176        public Builder setName(final String name) {
177            this.name = name;
178            return this;
179        }
180
181        public Builder setPassword(final char[] password) {
182            this.password = password;
183            return this;
184        }
185
186        /**
187         * @deprecated Use setPassword(char[])
188         */
189        @Deprecated
190        public Builder setPassword(final String password) {
191            this.password = password == null ? null : password.toCharArray();
192            return this;
193        }
194
195        public Builder setProviderUrl(final String providerUrl) {
196            this.providerUrl = providerUrl;
197            return this;
198        }
199
200        public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) {
201            this.reconnectIntervalMillis = reconnectIntervalMillis;
202            return this;
203        }
204
205        public Builder setSecurityCredentials(final String securityCredentials) {
206            this.securityCredentials = securityCredentials;
207            return this;
208        }
209
210        public Builder setSecurityPrincipalName(final String securityPrincipalName) {
211            this.securityPrincipalName = securityPrincipalName;
212            return this;
213        }
214
215        public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
216            this.urlPkgPrefixes = urlPkgPrefixes;
217            return this;
218        }
219
220        /**
221         * @deprecated Use {@link #setUserName(String)}.
222         */
223        @Deprecated
224        public Builder setUsername(final String username) {
225            this.userName = username;
226            return this;
227        }
228
229        public Builder setUserName(final String userName) {
230            this.userName = userName;
231            return this;
232        }
233
234        /**
235         * Does not include the password.
236         */
237        @Override
238        public String toString() {
239            return "Builder [name=" + name + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl
240                    + ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName
241                    + ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName
242                    + ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout="
243                    + layout + ", filter=" + filter + ", ignoreExceptions=" + ignoreExceptions + ", jmsManager="
244                    + jmsManager + "]";
245        }
246
247    }
248
249    @PluginBuilderFactory
250    public static Builder newBuilder() {
251        return new Builder();
252    }
253
254    private volatile JmsManager manager;
255
256    /**
257     *
258     * @throws JMSException
259     *             not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
260     */
261    protected JmsAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
262            final boolean ignoreExceptions, final JmsManager manager) throws JMSException {
263        super(name, filter, layout, ignoreExceptions);
264        this.manager = manager;
265    }
266
267    @Override
268    public void append(final LogEvent event) {
269        this.manager.send(event, getLayout().toSerializable(event));
270    }
271
272    public JmsManager getManager() {
273        return manager;
274    }
275
276    @Override
277    public boolean stop(final long timeout, final TimeUnit timeUnit) {
278        setStopping();
279        boolean stopped = super.stop(timeout, timeUnit, false);
280        stopped &= this.manager.stop(timeout, timeUnit);
281        setStopped();
282        return stopped;
283    }
284
285}