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        // Programmatic access only for now.
092        private JmsManager jmsManager;
093
094        private Builder() {
095        }
096
097        @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender
098        @Override
099        public JmsAppender build() {
100            JmsManager actualJmsManager = jmsManager;
101            JmsManagerConfiguration configuration = null;
102            if (actualJmsManager == null) {
103                final Properties jndiProperties = JndiManager.createProperties(factoryName, providerUrl, urlPkgPrefixes,
104                        securityPrincipalName, securityCredentials, null);
105                configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName,
106                        userName, password, false, reconnectIntervalMillis);
107                actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration);
108            }
109            if (actualJmsManager == null) {
110                // JmsManagerFactory has already logged an ERROR.
111                return null;
112            }
113            final Layout<? extends Serializable> layout = getLayout();
114            if (layout == null) {
115                LOGGER.error("No layout provided for JmsAppender");
116                return null;
117            }
118            try {
119                return new JmsAppender(getName(), getFilter(), layout, isIgnoreExceptions(), getPropertyArray(),
120                        actualJmsManager);
121            } catch (final JMSException e) {
122                // Never happens since the ctor no longer actually throws a JMSException.
123                throw new IllegalStateException(e);
124            }
125        }
126
127        public Builder setDestinationBindingName(final String destinationBindingName) {
128            this.destinationBindingName = destinationBindingName;
129            return this;
130        }
131
132        public Builder setFactoryBindingName(final String factoryBindingName) {
133            this.factoryBindingName = factoryBindingName;
134            return this;
135        }
136
137        public Builder setFactoryName(final String factoryName) {
138            this.factoryName = factoryName;
139            return this;
140        }
141
142        public Builder setImmediateFail(final boolean immediateFail) {
143            this.immediateFail = immediateFail;
144            return this;
145        }
146
147        public Builder setJmsManager(final JmsManager jmsManager) {
148            this.jmsManager = jmsManager;
149            return this;
150        }
151
152        public Builder setPassword(final char[] password) {
153            this.password = password;
154            return this;
155        }
156
157        /**
158         * @deprecated Use setPassword(char[])
159         */
160        @Deprecated
161        public Builder setPassword(final String password) {
162            this.password = password == null ? null : password.toCharArray();
163            return this;
164        }
165
166        public Builder setProviderUrl(final String providerUrl) {
167            this.providerUrl = providerUrl;
168            return this;
169        }
170
171        public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) {
172            this.reconnectIntervalMillis = reconnectIntervalMillis;
173            return this;
174        }
175
176        public Builder setSecurityCredentials(final String securityCredentials) {
177            this.securityCredentials = securityCredentials;
178            return this;
179        }
180
181        public Builder setSecurityPrincipalName(final String securityPrincipalName) {
182            this.securityPrincipalName = securityPrincipalName;
183            return this;
184        }
185
186        public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
187            this.urlPkgPrefixes = urlPkgPrefixes;
188            return this;
189        }
190
191        /**
192         * @deprecated Use {@link #setUserName(String)}.
193         */
194        @Deprecated
195        public Builder setUsername(final String username) {
196            this.userName = username;
197            return this;
198        }
199
200        public Builder setUserName(final String userName) {
201            this.userName = userName;
202            return this;
203        }
204
205        /**
206         * Does not include the password.
207         */
208        @Override
209        public String toString() {
210            return "Builder [name=" + getName() + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl
211                    + ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName
212                    + ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName
213                    + ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout="
214                    + getLayout() + ", filter=" + getFilter() + ", ignoreExceptions=" + isIgnoreExceptions()
215                    + ", jmsManager=" + jmsManager + "]";
216        }
217
218    }
219
220    @PluginBuilderFactory
221    public static Builder newBuilder() {
222        return new Builder();
223    }
224
225    private volatile JmsManager manager;
226
227    /**
228     *
229     * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
230     */
231    protected JmsAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
232            final boolean ignoreExceptions, final Property[] properties, final JmsManager manager) throws JMSException {
233        super(name, filter, layout, ignoreExceptions, properties);
234        this.manager = manager;
235    }
236
237    /**
238     *
239     * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0
240     * @deprecated
241     */
242    @Deprecated
243    protected JmsAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
244            final boolean ignoreExceptions, final JmsManager manager) throws JMSException {
245        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
246        this.manager = manager;
247    }
248
249    @Override
250    public void append(final LogEvent event) {
251        this.manager.send(event, toSerializable(event));
252    }
253
254    public JmsManager getManager() {
255        return manager;
256    }
257
258    @Override
259    public boolean stop(final long timeout, final TimeUnit timeUnit) {
260        setStopping();
261        boolean stopped = super.stop(timeout, timeUnit, false);
262        stopped &= this.manager.stop(timeout, timeUnit);
263        setStopped();
264        return stopped;
265    }
266
267}