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.activemq.transport;
018
019import java.io.IOException;
020
021import javax.management.ObjectName;
022
023import org.apache.activemq.broker.jmx.AnnotatedMBean;
024import org.apache.activemq.broker.jmx.ManagementContext;
025import org.apache.activemq.util.IOExceptionSupport;
026import org.apache.activemq.util.LogWriterFinder;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import static org.apache.activemq.TransportLoggerSupport.defaultJmxPort;
031
032/**
033 * Singleton class to create TransportLogger objects.
034 * When the method getInstance() is called for the first time,
035 * a TransportLoggerControlMBean is created and registered.
036 * This MBean permits enabling and disabling the logging for
037 * all TransportLogger objects at once.
038 *
039 * @author David Martin Clavo david(dot)martin(dot)clavo(at)gmail.com
040 *
041 * @see TransportLoggerControlMBean
042 */
043public class TransportLoggerFactory {
044
045    private static final Logger LOG = LoggerFactory.getLogger(TransportLoggerFactory.class);
046
047    private static TransportLoggerFactory instance;
048    private static int lastId=0;
049    private static final LogWriterFinder logWriterFinder = new LogWriterFinder("META-INF/services/org/apache/activemq/transport/logwriters/");
050
051    /**
052     * LogWriter that will be used if none is specified.
053     */
054    public static String defaultLogWriterName = "default";
055    /**
056     * If transport logging is enabled, it will be possible to control
057     * the transport loggers or not based on this value
058     */
059    private static boolean defaultDynamicManagement = false;
060    /**
061     * If transport logging is enabled, the transport loggers will initially
062     * output or not depending on this value.
063     * This setting only has a meaning if
064     */
065    private static boolean defaultInitialBehavior = true;
066
067    private boolean transportLoggerControlCreated = false;
068    private ManagementContext managementContext;
069    private ObjectName objectName;
070
071    /**
072     * Private constructor.
073     */
074    private TransportLoggerFactory() {
075    }
076
077    /**
078     * Returns a TransportLoggerFactory object which can be used to create TransportLogger objects.
079     * @return a TransportLoggerFactory object
080     */
081    public static synchronized TransportLoggerFactory getInstance() {
082        if (instance == null) {
083            instance = new TransportLoggerFactory();
084        }
085        return instance;
086    }
087
088    public void stop() {
089        try {
090            if (this.transportLoggerControlCreated) {
091                this.managementContext.unregisterMBean(this.objectName);
092                this.managementContext.stop();
093                this.managementContext = null;
094            }
095        } catch (Exception e) {
096            LOG.error("TransportLoggerFactory could not be stopped, reason: " + e, e);
097        }
098
099    }
100
101    /**
102     * Creates a TransportLogger object, that will be inserted in the Transport Stack.
103     * Uses the default initial behavior, the default log writer, and creates a new
104     * log4j object to be used by the TransportLogger.
105     * @param next The next Transport layer in the Transport stack.
106     * @return A TransportLogger object.
107     * @throws IOException
108     */
109    public TransportLogger createTransportLogger(Transport next) throws IOException {
110        int id = getNextId();
111        return createTransportLogger(next, id, createLog(id), defaultLogWriterName, defaultDynamicManagement, defaultInitialBehavior, defaultJmxPort);
112    }
113
114    /**
115     * Creates a TransportLogger object, that will be inserted in the Transport Stack.
116     * Uses the default initial behavior and the default log writer.
117     * @param next The next Transport layer in the Transport stack.
118     * @param log The log4j log that will be used by the TransportLogger.
119     * @return A TransportLogger object.
120     * @throws IOException
121     */
122    public TransportLogger createTransportLogger(Transport next, Logger log) throws IOException {
123        return createTransportLogger(next, getNextId(), log, defaultLogWriterName, defaultDynamicManagement, defaultInitialBehavior, defaultJmxPort);
124    }
125
126    /**
127     * Creates a TransportLogger object, that will be inserted in the Transport Stack.
128     * Creates a new log4j object to be used by the TransportLogger.
129     * @param next The next Transport layer in the Transport stack.
130     * @param startLogging Specifies if this TransportLogger should be initially active or not.
131     * @param logWriterName The name or the LogWriter to be used. Different log writers can output
132     * logs with a different format.
133     * @return A TransportLogger object.
134     * @throws IOException
135     */
136    public TransportLogger createTransportLogger(Transport next, String logWriterName,
137            boolean useJmx, boolean startLogging, int jmxport) throws IOException {
138        int id = -1; // new default to single logger
139        // allow old behaviour with incantation
140        if (!useJmx && jmxport != defaultJmxPort) {
141            id = getNextId();
142        }
143        return createTransportLogger(next, id, createLog(id), logWriterName, useJmx, startLogging, jmxport);
144    }
145
146
147
148    /**
149     * Creates a TransportLogger object, that will be inserted in the Transport Stack.
150     * @param next The next Transport layer in the Transport stack.
151     * @param id The id of the transport logger.
152     * @param log The log4j log that will be used by the TransportLogger.
153     * @param logWriterName The name or the LogWriter to be used. Different log writers can output
154     * @param dynamicManagement Specifies if JMX will be used to switch on/off the TransportLogger to be created.
155     * @param startLogging Specifies if this TransportLogger should be initially active or not. Only has a meaning if
156     * dynamicManagement = true.
157     * @param jmxport the port to be used by the JMX server. It should only be different from 1099 (broker's default JMX port)
158     * when it's a client that is using Transport Logging. In a broker, if the port is different from 1099, 2 JMX servers will
159     * be created, both identical, with all the MBeans.
160     * @return A TransportLogger object.
161     * @throws IOException
162     */
163    public TransportLogger createTransportLogger(Transport next, int id, Logger log,
164            String logWriterName, boolean dynamicManagement, boolean startLogging, int jmxport) throws IOException {
165        try {
166            LogWriter logWriter = logWriterFinder.newInstance(logWriterName);
167            if (id == -1) {
168                logWriter.setPrefix(String.format("%08X: ", getNextId()));
169            }
170            TransportLogger tl =  new TransportLogger (next, log, startLogging, logWriter);
171            if (dynamicManagement) {
172                synchronized (this) {
173                    if (!this.transportLoggerControlCreated) {
174                        this.createTransportLoggerControl(jmxport);
175                    }
176                }
177                TransportLoggerView tlv = new TransportLoggerView(tl, next.toString(), id, this.managementContext);
178                tl.setView(tlv);
179            }
180            return tl;
181        } catch (Throwable e) {
182            throw IOExceptionSupport.create("Could not create log writer object for: " + logWriterName + ", reason: " + e, e);
183        }
184    }
185
186    synchronized private static int getNextId() {
187        return ++lastId;
188    }
189
190    private static Logger createLog(int id) {
191        return LoggerFactory.getLogger(TransportLogger.class.getName()+".Connection" + (id > 0 ? ":"+id : "" ));
192    }
193
194    /**
195     * Starts the management context.
196     * Creates and registers a TransportLoggerControl MBean which enables the user
197     * to enable/disable logging for all transport loggers at once.
198     */
199     private void createTransportLoggerControl(int port) {
200         try {
201             this.managementContext = new ManagementContext();
202             this.managementContext.setConnectorPort(port);
203             this.managementContext.start();
204         } catch (Exception e) {
205             LOG.error("Management context could not be started, reason: " + e, e);
206         }
207
208         try {
209             this.objectName = new ObjectName(this.managementContext.getJmxDomainName()+":"+ "Type=TransportLoggerControl");
210             AnnotatedMBean.registerMBean(this.managementContext, new TransportLoggerControl(this.managementContext),this.objectName);
211
212             this.transportLoggerControlCreated = true;
213
214         } catch (Exception e) {
215             LOG.error("TransportLoggerControlMBean could not be registered, reason: " + e, e);
216         }
217     }
218
219}