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.Property;
035import org.apache.logging.log4j.core.config.plugins.Plugin;
036import org.apache.logging.log4j.core.config.plugins.PluginAliases;
037import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
038import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
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<B extends Builder<B>> extends AbstractAppender.Builder<B>
051            implements org.apache.logging.log4j.core.util.Builder<JmsAppender> {
052
053        public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000;
054
055        @PluginBuilderAttribute
056        private String factoryName;
057
058        @PluginBuilderAttribute
059        private String providerUrl;
060
061        @PluginBuilderAttribute
062        private String urlPkgPrefixes;
063
064        @PluginBuilderAttribute
065        private String securityPrincipalName;
066
067        @PluginBuilderAttribute(sensitive = true)
068        private String securityCredentials;
069
070        @PluginBuilderAttribute
071        @Required(message = "A javax.jms.ConnectionFactory JNDI name must be specified")
072        private String factoryBindingName;
073
074        @PluginBuilderAttribute
075        @PluginAliases({ "queueBindingName", "topicBindingName" })
076        @Required(message = "A javax.jms.Destination JNDI name must be specified")
077        private String destinationBindingName;
078
079        @PluginBuilderAttribute
080        private String userName;
081
082        @PluginBuilderAttribute(sensitive = true)
083        private char[] password;
084
085        @PluginBuilderAttribute
086        private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS;
087
088        @PluginBuilderAttribute
089        private boolean immediateFail;
090
091        @PluginBuilderAttribute
092        private String allowedLdapClasses;
093
094        @PluginBuilderAttribute
095        private String allowedLdapHosts;
096
097        @PluginBuilderAttribute
098        private String allowedJndiProtocols;
099
100        // Programmatic access only for now.
101        private JmsManager jmsManager;
102
103        private Builder() {
104        }
105
106        @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender
107        @Override
108        public JmsAppender build() {
109            JmsManager actualJmsManager = jmsManager;
110            JmsManagerConfiguration configuration = null;
111            if (actualJmsManager == null) {
112                Properties additionalProperties = null;
113                if (allowedLdapClasses != null || allowedLdapHosts != null) {
114                    additionalProperties = new Properties();
115                    if (allowedLdapHosts != null) {
116                        additionalProperties.put(JndiManager.ALLOWED_HOSTS, allowedLdapHosts);
117                    }
118                    if (allowedLdapClasses != null) {
119                        additionalProperties.put(JndiManager.ALLOWED_CLASSES, allowedLdapClasses);
120                    }
121                    if (allowedJndiProtocols != null) {
122                        additionalProperties.put(JndiManager.ALLOWED_PROTOCOLS, allowedJndiProtocols);
123                    }
124                }
125                final Properties jndiProperties = JndiManager.createProperties(factoryName, providerUrl, urlPkgPrefixes,
126                        securityPrincipalName, securityCredentials, additionalProperties);
127                configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName,
128                        userName, password, false, reconnectIntervalMillis);
129                actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration);
130            }
131            if (actualJmsManager == null) {
132                // JmsManagerFactory has already logged an ERROR.
133                return null;
134            }
135            final Layout<? extends Serializable> layout = getLayout();
136            if (layout == null) {
137                LOGGER.error("No layout provided for JmsAppender");
138                return null;
139            }
140            try {
141                return new JmsAppender(getName(), getFilter(), layout, isIgnoreExceptions(), getPropertyArray(),
142                        actualJmsManager);
143            } catch (final JMSException e) {
144                // Never happens since the ctor no longer actually throws a JMSException.
145                throw new IllegalStateException(e);
146            }
147        }
148
149        public Builder setDestinationBindingName(final String destinationBindingName) {
150            this.destinationBindingName = destinationBindingName;
151            return this;
152        }
153
154        public Builder setFactoryBindingName(final String factoryBindingName) {
155            this.factoryBindingName = factoryBindingName;
156            return this;
157        }
158
159        public Builder setFactoryName(final String factoryName) {
160            this.factoryName = factoryName;
161            return this;
162        }
163
164        public Builder setImmediateFail(final boolean immediateFail) {
165            this.immediateFail = immediateFail;
166            return this;
167        }
168
169        public Builder setJmsManager(final JmsManager jmsManager) {
170            this.jmsManager = jmsManager;
171            return this;
172        }
173
174        public Builder setPassword(final char[] password) {
175            this.password = password;
176            return this;
177        }
178
179        /**
180         * @deprecated Use setPassword(char[])
181         */
182        @Deprecated
183        public Builder setPassword(final String password) {
184            this.password = password == null ? null : password.toCharArray();
185            return this;
186        }
187
188        public Builder setProviderUrl(final String providerUrl) {
189            this.providerUrl = providerUrl;
190            return this;
191        }
192
193        public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) {
194            this.reconnectIntervalMillis = reconnectIntervalMillis;
195            return this;
196        }
197
198        public Builder setSecurityCredentials(final String securityCredentials) {
199            this.securityCredentials = securityCredentials;
200            return this;
201        }
202
203        public Builder setSecurityPrincipalName(final String securityPrincipalName) {
204            this.securityPrincipalName = securityPrincipalName;
205            return this;
206        }
207
208        public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
209            this.urlPkgPrefixes = urlPkgPrefixes;
210            return this;
211        }
212
213        /**
214         * @deprecated Use {@link #setUserName(String)}.
215         */
216        @Deprecated
217        public Builder setUsername(final String username) {
218            this.userName = username;
219            return this;
220        }
221
222        public Builder setUserName(final String userName) {
223            this.userName = userName;
224            return this;
225        }
226
227        public Builder setAllowedLdapClasses(final String allowedLdapClasses) {
228            this.allowedLdapClasses = allowedLdapClasses;
229            return this;
230        }
231
232        public Builder setAllowedLdapHosts(final String allowedLdapHosts) {
233            this.allowedLdapHosts = allowedLdapHosts;
234            return this;
235        }
236
237        public Builder setAllowedJndiProtocols(final String allowedJndiProtocols) {
238            this.allowedJndiProtocols = allowedJndiProtocols;
239            return this;
240        }
241
242        /**
243         * Does not include the password.
244         */
245        @Override
246        public String toString() {
247            return "Builder [name=" + getName() + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl
248                    + ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName
249                    + ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName
250                    + ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout="
251                    + getLayout() + ", filter=" + getFilter() + ", ignoreExceptions=" + isIgnoreExceptions()
252                    + ", jmsManager=" + jmsManager + ", allowedLdapClasses=" + allowedLdapClasses
253                    + ", allowedLdapHosts=" + allowedLdapHosts + ", allowedJndiProtocols=" + allowedJndiProtocols + "]";
254        }
255
256    }
257
258    @PluginBuilderFactory
259    public static Builder newBuilder() {
260        return new Builder();
261    }
262
263    private volatile JmsManager manager;
264
265    /**
266     *
267     * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
268     */
269    protected JmsAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
270            final boolean ignoreExceptions, final Property[] properties, final JmsManager manager) throws JMSException {
271        super(name, filter, layout, ignoreExceptions, properties);
272        this.manager = manager;
273    }
274
275    /**
276     *
277     * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
278     * @deprecated
279     */
280    @Deprecated
281    protected JmsAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
282            final boolean ignoreExceptions, final JmsManager manager) throws JMSException {
283        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
284        this.manager = manager;
285    }
286
287    @Override
288    public void append(final LogEvent event) {
289        this.manager.send(event, toSerializable(event));
290    }
291
292    public JmsManager getManager() {
293        return manager;
294    }
295
296    @Override
297    public boolean stop(final long timeout, final TimeUnit timeUnit) {
298        setStopping();
299        boolean stopped = super.stop(timeout, timeUnit, false);
300        stopped &= this.manager.stop(timeout, timeUnit);
301        setStopped();
302        return stopped;
303    }
304
305}