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