View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2006, 2013
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Alexander Wolf-Reber, IBM, a.wolf-reber@de.ibm.com
12   * 
13   * Change History
14   * Flag       Date        Prog         Description
15   *------------------------------------------------------------------------------- 
16   * 1565892    2006-11-14  lupusalex    Make SBLIM client JSR48 compliant
17   * 1711092    2006-05-02  lupusalex    Some fixes/additions of log&trace messages
18   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
19   * 2204488 	  2008-10-28  raman_arora  Fix code to remove compiler warnings
20   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
21   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
22   * 2763216    2009-04-14  blaschke-oss Code cleanup: visible spelling/grammar errors
23   * 3001345    2010-05-18  blaschke-oss File handle leaks in HttpSocketFactory and LogAndTraceBroker
24   * 3027618    2010-07-14  blaschke-oss Close files/readers in finally blocks
25   * 3154232    2011-01-13  blaschke-oss EmbeddedObject misspelled in javadoc
26   * 3252669    2011-03-28  blaschke-oss setXmlTraceStream blindly closes previous stream
27   * 3400209    2011-08-31  blaschke-oss Highlighted Static Analysis (PMD) issues
28   * 3469018    2012-01-03  blaschke-oss Properties not passed to CIMIndicationHandler
29   * 3484014    2012-02-03  blaschke-oss Add LogAndTraceBroker.isLoggable for message/trace
30   * 3489638    2012-02-28  blaschke-oss PERF: Bottleneck in LogAndTraceBroker.java - getCaller()
31   * 3554738    2012-08-16  blaschke-oss dump CIM xml by LogAndTraceBroker.trace()
32   * 3576396    2012-10-11  blaschke-oss Improve logging of config file name
33   * 3596303    2013-01-04  blaschke-oss windows http response WWW-Authenticate: Negotiate fails
34   *    2652    2013-07-26  blaschke-oss LogAndTraceBroker.setXmlTraceStream should not close previous stream
35   *    2651    2013-07-31  blaschke-oss IOException when tracing the cimxml
36   */
37  
38  package org.metricshub.wbem.sblim.cimclient.internal.logging;
39  
40  /*-
41   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
42   * WBEM Java Client
43   * ჻჻჻჻჻჻
44   * Copyright 2023 - 2025 MetricsHub
45   * ჻჻჻჻჻჻
46   * Licensed under the Apache License, Version 2.0 (the "License");
47   * you may not use this file except in compliance with the License.
48   * You may obtain a copy of the License at
49   *
50   *      http://www.apache.org/licenses/LICENSE-2.0
51   *
52   * Unless required by applicable law or agreed to in writing, software
53   * distributed under the License is distributed on an "AS IS" BASIS,
54   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55   * See the License for the specific language governing permissions and
56   * limitations under the License.
57   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
58   */
59  
60  import java.io.FileOutputStream;
61  import java.io.IOException;
62  import java.io.InputStream;
63  import java.io.OutputStream;
64  import java.text.MessageFormat;
65  import java.util.ArrayList;
66  import java.util.List;
67  import java.util.Properties;
68  import java.util.logging.ConsoleHandler;
69  import java.util.logging.FileHandler;
70  import java.util.logging.Handler;
71  import java.util.logging.Level;
72  import java.util.logging.LogRecord;
73  import java.util.logging.Logger;
74  import org.metricshub.wbem.sblim.cimclient.CIMXMLTraceListener;
75  import org.metricshub.wbem.sblim.cimclient.LogAndTraceManager;
76  import org.metricshub.wbem.sblim.cimclient.LogListener;
77  import org.metricshub.wbem.sblim.cimclient.TraceListener;
78  import org.metricshub.wbem.sblim.cimclient.WBEMConfigurationProperties;
79  import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConfiguration;
80  import org.metricshub.wbem.sblim.cimclient.internal.util.WBEMConstants;
81  
82  /**
83   * Class LogAndTraceBroker is the central class that implements the logging and
84   * tracing of the CIM Client. It manages the collections of the internal log and
85   * trace listeners. It sets up the application independent logging. It provides
86   * the API to send log and trace messages and forwards them to the appropriate
87   * listeners.
88   *
89   */
90  public class LogAndTraceBroker {
91  	private static final String TRACE_LOGGER = "org.metricshub.wbem.sblim.cimclient.trace";
92  
93  	private static final String FILE_LOGGER = "org.metricshub.wbem.sblim.cimclient.file";
94  
95  	private static final String CONSOLE_LOGGER = "org.metricshub.wbem.sblim.cimclient.console";
96  
97  	private static LogAndTraceBroker cBroker = new LogAndTraceBroker();
98  
99  	/**
100 	 * Returns the singleton instance of the broker
101 	 *
102 	 * @return The broker instance
103 	 */
104 	public static LogAndTraceBroker getBroker() {
105 		return cBroker;
106 	}
107 
108 	/**
109 	 * Returns if the logging framework has been initialized. This method is
110 	 * used by the <code>WBEMConfiguration</code> class to determine if the
111 	 * logging is already up. The <code>WBEMConfiguration</code> is initialized
112 	 * before the logging, so methods in this class cannot assume the logging to
113 	 * be up and running.
114 	 *
115 	 * @return <code>true</code> if the logging is up, <code>false</code>
116 	 *         otherwise
117 	 */
118 	public static boolean isLoggingStarted() {
119 		return cBroker != null;
120 	}
121 
122 	private volatile ArrayList<LogListener> iLogListeners;
123 
124 	private volatile ArrayList<TraceListener> iTraceListeners;
125 
126 	private volatile ArrayList<CIMXMLTraceListener> iCIMXMLTraceListeners;
127 
128 	private String iProductName = "SBLIM CIM Client for Java";
129 
130 	private String iCopyright = "COPYRIGHT (C) 2006, 2013 IBM Corp.";
131 
132 	private String iVersion = "?";
133 
134 	private String iBuildDate = "?";
135 
136 	private String iBuildTime = "?";
137 
138 	private OutputStream iXmlTraceStream = null;
139 
140 	private OutputStream iXmlTraceFile = null;
141 
142 	private final String iTHIS_CLASS = this.getClass().getName();
143 
144 	private final String iTHROWABLE = Throwable.class.getName();
145 
146 	private int iInternalLevelConsole = Level.OFF.intValue();
147 
148 	private int iInternalLevelLogFile = Level.OFF.intValue();
149 
150 	private int iInternalLevelTraceFile = Level.OFF.intValue();
151 
152 	private LogListener iInternalListenerLogConsole = null;
153 
154 	private LogListener iInternalListenerLogFile = null;
155 
156 	private TraceListener iInternalListenerTraceConsole = null;
157 
158 	private TraceListener iInternalListenerTraceFile = null;
159 
160 	private int iNumInternalLogListeners = 0;
161 
162 	private int iNumExternalLogListeners = 0;
163 
164 	private int iNumInternalTraceListeners = 0;
165 
166 	private int iNumExternalTraceListeners = 0;
167 
168 	private LogAndTraceBroker() {
169 		this.iLogListeners = new ArrayList<LogListener>();
170 		this.iTraceListeners = new ArrayList<TraceListener>();
171 		this.iCIMXMLTraceListeners = new ArrayList<CIMXMLTraceListener>();
172 		loadVersionTxt();
173 		registerInternalListeners();
174 		initXmlTraceFile();
175 	}
176 
177 	@Override
178 	protected void finalize() throws Throwable {
179 		try {
180 			if (
181 				this.iXmlTraceFile != null &&
182 				(!this.iXmlTraceFile.equals(System.out)) &&
183 				(!this.iXmlTraceFile.equals(System.err))
184 			) this.iXmlTraceFile.close();
185 		} catch (IOException e) {
186 			// bad luck
187 		} finally {
188 			this.iXmlTraceFile = null;
189 			super.finalize();
190 		}
191 	}
192 
193 	/**
194 	 * Registers the listeners for our internal loggers
195 	 */
196 	public void registerInternalListeners() {
197 		try {
198 			Level level = WBEMConfiguration.getGlobalConfiguration().getLogConsoleLevel();
199 			String type = WBEMConfiguration.getGlobalConfiguration().getLogConsoleType();
200 			if (level.intValue() < Level.OFF.intValue() && WBEMConstants.MESSAGE.equals(type)) {
201 				final Logger logger = Logger.getLogger(CONSOLE_LOGGER);
202 				final Handler handler = new ConsoleHandler();
203 				handler.setFormatter(new LogFormatter());
204 				handler.setLevel(level);
205 				logger.addHandler(handler);
206 				logger.setLevel(level);
207 				logger.setUseParentHandlers(false);
208 				this.iInternalLevelConsole = level.intValue();
209 				if (this.iInternalListenerLogConsole != null) removeLogListener(this.iInternalListenerLogConsole);
210 				this.iInternalListenerLogConsole =
211 					new LogListener() {
212 
213 						public void log(Level pLevel, String pMessageKey, String pMessage, Object[] pParameters) {
214 							LogRecord record = new LogRecord(pLevel, pMessageKey + " " + pMessage);
215 							record.setParameters(pParameters);
216 							logger.log(record);
217 						}
218 					};
219 				addLogListener(this.iInternalListenerLogConsole);
220 			}
221 		} catch (Exception e) {
222 			// Don't crash for logging
223 		}
224 		try {
225 			Level level = WBEMConfiguration.getGlobalConfiguration().getLogFileLevel();
226 			String location = WBEMConfiguration.getGlobalConfiguration().getLogFileLocation();
227 			int size = WBEMConfiguration.getGlobalConfiguration().getLogFileSizeLimit();
228 			int count = WBEMConfiguration.getGlobalConfiguration().getLogFileCount();
229 			if (level.intValue() < Level.OFF.intValue()) {
230 				final Logger logger = Logger.getLogger(FILE_LOGGER);
231 				final Handler handler = new FileHandler(location, size, count);
232 				handler.setFormatter(new LogFormatter());
233 				handler.setLevel(level);
234 				logger.addHandler(handler);
235 				logger.setLevel(level);
236 				logger.setUseParentHandlers(false);
237 				this.iInternalLevelLogFile = level.intValue();
238 				if (this.iInternalListenerLogFile != null) removeLogListener(this.iInternalListenerLogFile);
239 				this.iInternalListenerLogFile =
240 					new LogListener() {
241 
242 						public void log(Level pLevel, String pMessageKey, String pMessage, Object[] pParameters) {
243 							LogRecord record = new LogRecord(pLevel, pMessageKey + " " + pMessage);
244 							record.setParameters(pParameters);
245 							logger.log(record);
246 						}
247 					};
248 				addLogListener(this.iInternalListenerLogFile);
249 			}
250 		} catch (Exception e) {
251 			// Don't crash for logging
252 		}
253 		try {
254 			Level level = WBEMConfiguration.getGlobalConfiguration().getLogConsoleLevel();
255 			String type = WBEMConfiguration.getGlobalConfiguration().getLogConsoleType();
256 			if (level.intValue() < Level.OFF.intValue() && WBEMConstants.TRACE.equals(type)) {
257 				final Logger logger = Logger.getLogger(CONSOLE_LOGGER);
258 				final Handler handler = new ConsoleHandler();
259 				handler.setFormatter(new TraceFormatter());
260 				handler.setLevel(level);
261 				logger.addHandler(handler);
262 				logger.setLevel(level);
263 				logger.setUseParentHandlers(false);
264 				this.iInternalLevelConsole = level.intValue();
265 				if (this.iInternalListenerTraceConsole != null) removeTraceListener(this.iInternalListenerTraceConsole);
266 				this.iInternalListenerTraceConsole =
267 					new TraceListener() {
268 
269 						public void trace(Level pLevel, StackTraceElement pOrigin, String pMessage) {
270 							LogRecord record = new LogRecord(pLevel, pMessage);
271 							record.setSourceMethodName(String.valueOf(pOrigin));
272 							logger.log(record);
273 						}
274 
275 						public void trace(Level pLevel, StackTraceElement pOrigin, String pMessage, Throwable pThrown) {
276 							LogRecord record = new LogRecord(pLevel, pMessage);
277 							record.setSourceMethodName(String.valueOf(pOrigin));
278 							record.setThrown(pThrown);
279 							logger.log(record);
280 						}
281 					};
282 				addTraceListener(this.iInternalListenerTraceConsole);
283 			}
284 		} catch (Exception e) {
285 			// Don't crash for logging
286 		}
287 		try {
288 			Level level = WBEMConfiguration.getGlobalConfiguration().getTraceFileLevel();
289 			String location = WBEMConfiguration.getGlobalConfiguration().getTraceFileLocation();
290 			int size = WBEMConfiguration.getGlobalConfiguration().getTraceFileSizeLimit();
291 			int count = WBEMConfiguration.getGlobalConfiguration().getTraceFileCount();
292 			if (level.intValue() < Level.OFF.intValue()) {
293 				final Logger logger = Logger.getLogger(TRACE_LOGGER);
294 				final Handler handler = new FileHandler(location, size, count);
295 				handler.setFormatter(new TraceFormatter());
296 				handler.setLevel(level);
297 				logger.addHandler(handler);
298 				logger.setLevel(level);
299 				logger.setUseParentHandlers(false);
300 				this.iInternalLevelTraceFile = level.intValue();
301 				if (this.iInternalListenerTraceFile != null) removeTraceListener(this.iInternalListenerTraceFile);
302 				this.iInternalListenerTraceFile =
303 					new TraceListener() {
304 
305 						public void trace(Level pLevel, StackTraceElement pOrigin, String pMessage) {
306 							LogRecord record = new LogRecord(pLevel, pMessage);
307 							record.setSourceMethodName(String.valueOf(pOrigin));
308 							logger.log(record);
309 						}
310 
311 						public void trace(Level pLevel, StackTraceElement pOrigin, String pMessage, Throwable pThrown) {
312 							LogRecord record = new LogRecord(pLevel, pMessage);
313 							record.setSourceMethodName(String.valueOf(pOrigin));
314 							record.setThrown(pThrown);
315 							logger.log(record);
316 						}
317 					};
318 				addTraceListener(this.iInternalListenerTraceFile);
319 			}
320 		} catch (Exception e) {
321 			// Don't crash for logging
322 		}
323 	}
324 
325 	/**
326 	 * Adds a listener for log messages. The listener will be notified of any
327 	 * log event. Uses copy on write to ensure concurrent read access.
328 	 *
329 	 * @param pListener
330 	 *            The listener
331 	 */
332 	public synchronized void addLogListener(LogListener pListener) {
333 		if (pListener == null) return;
334 		sendGreetings(pListener);
335 		ArrayList<LogListener> newListeners = new ArrayList<LogListener>(this.iLogListeners);
336 		newListeners.add(pListener);
337 		this.iLogListeners = newListeners;
338 
339 		if (
340 			(this.iInternalListenerLogFile != null && this.iInternalListenerLogFile.equals(pListener)) ||
341 			(this.iInternalListenerLogConsole != null && this.iInternalListenerLogConsole.equals(pListener))
342 		) {
343 			this.iNumInternalLogListeners++;
344 		} else {
345 			this.iNumExternalLogListeners++;
346 		}
347 	}
348 
349 	private void loadVersionTxt() {
350 		InputStream is = null;
351 		try {
352 			Properties version = new Properties();
353 			is = LogAndTraceManager.class.getResourceAsStream("version.txt");
354 			version.load(is);
355 			this.iProductName = version.getProperty("PRODUCTNAME");
356 			this.iCopyright = version.getProperty("COPYRIGHT");
357 			this.iVersion = version.getProperty("VERSION");
358 			this.iBuildDate = version.getProperty("BUILDDATE");
359 			this.iBuildTime = version.getProperty("BUILDTIME");
360 		} catch (Exception e) {
361 			// nothing to do
362 		} finally {
363 			if (is != null) {
364 				try {
365 					is.close();
366 				} catch (IOException e) {
367 					// nothing ro do
368 				}
369 			}
370 		}
371 	}
372 
373 	private void sendGreetings(LogListener pListener) {
374 		pListener.log(
375 			MessageLoader.getLevel(Messages.GREETING),
376 			Messages.GREETING,
377 			MessageLoader.getLocalizedMessage(Messages.GREETING),
378 			new Object[] { this.iProductName, this.iCopyright }
379 		);
380 		pListener.log(
381 			MessageLoader.getLevel(Messages.RELEASE),
382 			Messages.RELEASE,
383 			MessageLoader.getLocalizedMessage(Messages.RELEASE),
384 			new Object[] { this.iVersion, this.iBuildDate, this.iBuildTime }
385 		);
386 		pListener.log(
387 			MessageLoader.getLevel(Messages.OS),
388 			Messages.OS,
389 			MessageLoader.getLocalizedMessage(Messages.OS),
390 			new Object[] { System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch") }
391 		);
392 		pListener.log(
393 			MessageLoader.getLevel(Messages.JRE),
394 			Messages.JRE,
395 			MessageLoader.getLocalizedMessage(Messages.JRE),
396 			new Object[] { System.getProperty("java.version"), System.getProperty("java.vendor") }
397 		);
398 		pListener.log(
399 			MessageLoader.getLevel(Messages.JVM),
400 			Messages.JVM,
401 			MessageLoader.getLocalizedMessage(Messages.JVM),
402 			new Object[] {
403 				System.getProperty("java.vm.name"),
404 				System.getProperty("java.vm.version"),
405 				System.getProperty("java.vm.vendor")
406 			}
407 		);
408 		pListener.log(
409 			MessageLoader.getLevel(Messages.CONFIGURATION_URL),
410 			Messages.CONFIGURATION_URL,
411 			MessageLoader.getLocalizedMessage(Messages.CONFIGURATION_URL),
412 			new Object[] { WBEMConfiguration.getActiveConfigFullURL() }
413 		);
414 		if (!WBEMConfiguration.isConfigurationLoadSuccessful()) {
415 			pListener.log(
416 				MessageLoader.getLevel(Messages.CONFIGURATION_LOAD_FAILED),
417 				Messages.CONFIGURATION_LOAD_FAILED,
418 				MessageLoader.getLocalizedMessage(Messages.CONFIGURATION_LOAD_FAILED),
419 				(Object[]) null
420 			);
421 			if (WBEMConfiguration.getConfigurationLoadException() != null) {
422 				pListener.log(
423 					MessageLoader.getLevel(Messages.EXCEPTION_DURING_CONFIGURATION_LOAD),
424 					Messages.EXCEPTION_DURING_CONFIGURATION_LOAD,
425 					MessageLoader.getLocalizedMessage(Messages.EXCEPTION_DURING_CONFIGURATION_LOAD),
426 					new Object[] { WBEMConfiguration.getConfigurationLoadException().getMessage() }
427 				);
428 			}
429 		}
430 	}
431 
432 	private void sendGreetings(TraceListener pListener) {
433 		StackTraceElement origin = new Throwable().getStackTrace()[0];
434 		pListener.trace(
435 			MessageLoader.getLevel(Messages.GREETING),
436 			origin,
437 			MessageFormat.format(
438 				MessageLoader.getMessage(Messages.GREETING),
439 				new Object[] { this.iProductName, this.iCopyright }
440 			)
441 		);
442 		pListener.trace(
443 			MessageLoader.getLevel(Messages.RELEASE),
444 			origin,
445 			MessageFormat.format(
446 				MessageLoader.getMessage(Messages.RELEASE),
447 				new Object[] { this.iVersion, this.iBuildDate, this.iBuildTime }
448 			)
449 		);
450 		pListener.trace(
451 			MessageLoader.getLevel(Messages.OS),
452 			origin,
453 			MessageFormat.format(
454 				MessageLoader.getMessage(Messages.OS),
455 				new Object[] { System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch") }
456 			)
457 		);
458 		pListener.trace(
459 			MessageLoader.getLevel(Messages.JRE),
460 			origin,
461 			MessageFormat.format(
462 				MessageLoader.getMessage(Messages.JRE),
463 				new Object[] { System.getProperty("java.version"), System.getProperty("java.vendor") }
464 			)
465 		);
466 		pListener.trace(
467 			MessageLoader.getLevel(Messages.JVM),
468 			origin,
469 			MessageFormat.format(
470 				MessageLoader.getMessage(Messages.JVM),
471 				new Object[] {
472 					System.getProperty("java.vm.name"),
473 					System.getProperty("java.vm.version"),
474 					System.getProperty("java.vm.vendor")
475 				}
476 			)
477 		);
478 		pListener.trace(
479 			MessageLoader.getLevel(Messages.CONFIGURATION_URL),
480 			origin,
481 			MessageFormat.format(
482 				MessageLoader.getMessage(Messages.CONFIGURATION_URL),
483 				new Object[] { WBEMConfiguration.getActiveConfigURL() }
484 			)
485 		);
486 		if (!WBEMConfiguration.isConfigurationLoadSuccessful()) {
487 			pListener.trace(
488 				MessageLoader.getLevel(Messages.CONFIGURATION_LOAD_FAILED),
489 				origin,
490 				MessageLoader.getMessage(Messages.CONFIGURATION_LOAD_FAILED)
491 			);
492 			if (WBEMConfiguration.getConfigurationLoadException() != null) {
493 				pListener.trace(
494 					MessageLoader.getLevel(Messages.EXCEPTION_DURING_CONFIGURATION_LOAD),
495 					origin,
496 					MessageLoader.getMessage(Messages.EXCEPTION_DURING_CONFIGURATION_LOAD),
497 					WBEMConfiguration.getConfigurationLoadException()
498 				);
499 			}
500 		}
501 	}
502 
503 	/**
504 	 * Remove a listener. This listener will not be notified of log events
505 	 * anymore.
506 	 *
507 	 * @param pListener
508 	 *            The listener
509 	 */
510 	public synchronized void removeLogListener(LogListener pListener) {
511 		ArrayList<LogListener> newListeners = new ArrayList<LogListener>(this.iLogListeners);
512 		if (!newListeners.remove(pListener)) return;
513 		this.iLogListeners = newListeners;
514 
515 		if (this.iInternalListenerLogFile != null && this.iInternalListenerLogFile.equals(pListener)) {
516 			// Removing internal log listener for file
517 			this.iInternalListenerLogFile = null;
518 			this.iNumInternalLogListeners--;
519 		} else if (this.iInternalListenerLogConsole != null && this.iInternalListenerLogConsole.equals(pListener)) {
520 			// Removing internal log listener for console
521 			this.iInternalListenerLogConsole = null;
522 			this.iNumInternalLogListeners--;
523 		} else {
524 			// Removing user log listener
525 			this.iNumExternalLogListeners--;
526 		}
527 	}
528 
529 	/**
530 	 * Removes all listeners. Caution this will also remove the internal console
531 	 * and file loggers.
532 	 */
533 	public synchronized void clearLogListeners() {
534 		this.iLogListeners = new ArrayList<LogListener>();
535 		removeHandlers(Logger.getLogger(CONSOLE_LOGGER));
536 		removeHandlers(Logger.getLogger(FILE_LOGGER));
537 
538 		this.iInternalListenerLogFile = null;
539 		this.iInternalListenerLogConsole = null;
540 		this.iNumInternalLogListeners = 0;
541 		this.iNumExternalLogListeners = 0;
542 	}
543 
544 	/**
545 	 * Gets the registered log listeners including the internal console and file
546 	 * loggers.
547 	 *
548 	 * @return The list of listeners
549 	 */
550 	public List<LogListener> getLogListeners() {
551 		return this.iLogListeners;
552 	}
553 
554 	/**
555 	 * Adds a listener for log messages. The listener will be notified of any
556 	 * trace event.
557 	 *
558 	 * @param pListener
559 	 *            The listener
560 	 */
561 	public synchronized void addTraceListener(TraceListener pListener) {
562 		if (pListener == null) return;
563 		sendGreetings(pListener);
564 		ArrayList<TraceListener> newListeners = new ArrayList<TraceListener>(this.iTraceListeners);
565 		newListeners.add(pListener);
566 		this.iTraceListeners = newListeners;
567 
568 		if (
569 			(this.iInternalListenerTraceFile != null && this.iInternalListenerTraceFile.equals(pListener)) ||
570 			(this.iInternalListenerTraceConsole != null && this.iInternalListenerTraceConsole.equals(pListener))
571 		) {
572 			this.iNumInternalTraceListeners++;
573 		} else {
574 			this.iNumExternalTraceListeners++;
575 		}
576 	}
577 
578 	/**
579 	 * Removes a listener. This listener will not be notified of trace events
580 	 * anymore.
581 	 *
582 	 * @param pListener
583 	 *            The listener
584 	 */
585 	public synchronized void removeTraceListener(TraceListener pListener) {
586 		ArrayList<TraceListener> newListeners = new ArrayList<TraceListener>(this.iTraceListeners);
587 		if (!newListeners.remove(pListener)) return;
588 		this.iTraceListeners = newListeners;
589 
590 		if (this.iInternalListenerTraceFile != null && this.iInternalListenerTraceFile.equals(pListener)) {
591 			// Removing internal tracelistener for file
592 			this.iInternalListenerTraceFile = null;
593 			this.iNumInternalTraceListeners--;
594 		} else if (this.iInternalListenerTraceConsole != null && this.iInternalListenerTraceConsole.equals(pListener)) {
595 			// Removing internal trace listener for console
596 			this.iInternalListenerTraceConsole = null;
597 			this.iNumInternalTraceListeners--;
598 		} else {
599 			// Removing user trace listener
600 			this.iNumExternalTraceListeners--;
601 		}
602 	}
603 
604 	/**
605 	 * Removes all listeners. Caution this will also remove the internal trace
606 	 * file listener.
607 	 */
608 	public synchronized void clearTraceListeners() {
609 		this.iTraceListeners = new ArrayList<TraceListener>();
610 		removeHandlers(Logger.getLogger(TRACE_LOGGER));
611 
612 		this.iInternalListenerTraceFile = null;
613 		this.iInternalListenerTraceConsole = null;
614 		this.iNumInternalTraceListeners = 0;
615 		this.iNumExternalTraceListeners = 0;
616 	}
617 
618 	/**
619 	 * Gets the registered trace listeners including the internal console and
620 	 * file loggers.
621 	 *
622 	 * @return A list of listeners
623 	 */
624 	public List<TraceListener> getTraceListeners() {
625 		return this.iTraceListeners;
626 	}
627 
628 	/**
629 	 * Adds a listener for CIM-XML trace messages. The listener will be notified
630 	 * of any CIM-XML trace event.
631 	 *
632 	 * @param pListener
633 	 *            The listener
634 	 */
635 	public synchronized void addCIMXMLTraceListener(CIMXMLTraceListener pListener) {
636 		if (pListener == null) return;
637 		ArrayList<CIMXMLTraceListener> newListeners = new ArrayList<CIMXMLTraceListener>(this.iCIMXMLTraceListeners);
638 		newListeners.add(pListener);
639 		this.iCIMXMLTraceListeners = newListeners;
640 	}
641 
642 	/**
643 	 * Removes a CIM-XML trace listener. This listener will not be notified of
644 	 * CIM-XML trace events anymore.
645 	 *
646 	 * @param pListener
647 	 *            The listener
648 	 */
649 	public synchronized void removeCIMXMLTraceListener(CIMXMLTraceListener pListener) {
650 		ArrayList<CIMXMLTraceListener> newListeners = new ArrayList<CIMXMLTraceListener>(this.iCIMXMLTraceListeners);
651 		if (!newListeners.remove(pListener)) return;
652 		this.iCIMXMLTraceListeners = newListeners;
653 	}
654 
655 	/**
656 	 * Removes all CIM-XML trace listeners.
657 	 */
658 	public synchronized void clearCIMXMLTraceListeners() {
659 		if (this.iCIMXMLTraceListeners.size() > 0) this.iCIMXMLTraceListeners = new ArrayList<CIMXMLTraceListener>();
660 	}
661 
662 	/**
663 	 * Gets the registered CIM-XML trace listeners.
664 	 *
665 	 * @return A list of listeners
666 	 */
667 	public List<CIMXMLTraceListener> getCIMXMLTraceListeners() {
668 		return this.iCIMXMLTraceListeners;
669 	}
670 
671 	/**
672 	 * Forwards a log/trace message to the registered log&trace listeners.
673 	 *
674 	 * @param pKey
675 	 *            The message identifier.
676 	 */
677 	public void message(String pKey) {
678 		message(pKey, (Object[]) null);
679 	}
680 
681 	/**
682 	 * Forwards a log/trace message to the registered log&trace listeners.
683 	 *
684 	 * @param pKey
685 	 *            The message identifier.
686 	 * @param pParameter
687 	 *            The parameter for the message
688 	 */
689 	public void message(String pKey, Object pParameter) {
690 		message(pKey, new Object[] { pParameter });
691 	}
692 
693 	/**
694 	 * Forwards a log/trace message to the registered log&trace listeners.
695 	 *
696 	 * @param pKey
697 	 *            The message identifier.
698 	 * @param pParameters
699 	 *            The parameters for the message
700 	 */
701 	public void message(String pKey, Object[] pParameters) {
702 		try {
703 			final String message = MessageLoader.getMessage(pKey);
704 			final String localMessage = MessageLoader.getLocalizedMessage(pKey);
705 			final Level level = MessageLoader.getLevel(pKey);
706 			if (isLoggableTrace(level)) {
707 				final List<TraceListener> traceListeners = getTraceListeners();
708 				StackTraceElement caller = getCaller();
709 				for (int i = 0; i < traceListeners.size(); ++i) {
710 					traceListeners.get(i).trace(level, caller, pKey + " " + MessageFormat.format(message, pParameters));
711 				}
712 			}
713 			final List<LogListener> logListeners = getLogListeners();
714 			for (int i = 0; i < logListeners.size(); ++i) {
715 				logListeners.get(i).log(level, pKey, localMessage, pParameters);
716 			}
717 		} catch (Exception e) {
718 			// don't crash for logging
719 		}
720 	}
721 
722 	/**
723 	 * Forwards a trace message to the registered trace listeners.
724 	 *
725 	 * @param pLevel
726 	 *            One of the three message level identifiers FINE, FINER and
727 	 *            FINEST
728 	 * @param pMessage
729 	 *            The message text
730 	 */
731 	public void trace(Level pLevel, String pMessage) {
732 		try {
733 			if (isLoggableTrace(pLevel)) {
734 				final List<TraceListener> traceListeners = getTraceListeners();
735 				StackTraceElement caller = getCaller();
736 				for (int i = 0; i < traceListeners.size(); ++i) {
737 					traceListeners.get(i).trace(pLevel, caller, pMessage);
738 				}
739 			}
740 		} catch (Exception e) {
741 			// don't crash for logging
742 		}
743 	}
744 
745 	/**
746 	 * Forwards a trace message to the registered trace listeners.
747 	 *
748 	 * @param pLevel
749 	 *            One of the three message level identifiers FINE, FINER and
750 	 *            FINEST
751 	 * @param pMessage
752 	 *            The message text
753 	 * @param pThrown
754 	 *            The throwable associated with the message
755 	 */
756 	public void trace(Level pLevel, String pMessage, Throwable pThrown) {
757 		try {
758 			if (isLoggableTrace(pLevel)) {
759 				final List<TraceListener> traceListeners = getTraceListeners();
760 				StackTraceElement caller = getCaller();
761 				for (int i = 0; i < traceListeners.size(); ++i) {
762 					traceListeners.get(i).trace(pLevel, caller, pMessage, pThrown);
763 				}
764 			}
765 		} catch (Exception e) {
766 			// don't crash for logging
767 		}
768 	}
769 
770 	/**
771 	 * Forwards a CIM-XML trace message to the registered CIM-XML trace
772 	 * listeners.
773 	 *
774 	 * @param pLevel
775 	 *            One of the message level identifiers, e.g. FINE
776 	 * @param pMessage
777 	 *            The CIM-XML message text
778 	 * @param pOutgoing
779 	 *            <code>true</code> if CIM-XML is outgoing (being sent from
780 	 *            client to server), <code>false</code> if CIM-XML is incoming
781 	 *            (being sent from server to client)
782 	 */
783 	public void traceCIMXML(Level pLevel, String pMessage, boolean pOutgoing) {
784 		try {
785 			if (this.iCIMXMLTraceListeners.size() > 0) {
786 				final List<CIMXMLTraceListener> traceListeners = getCIMXMLTraceListeners();
787 				for (int i = 0; i < traceListeners.size(); ++i) {
788 					traceListeners.get(i).traceCIMXML(pLevel, pMessage, pOutgoing);
789 				}
790 			}
791 		} catch (Exception e) {
792 			// don't crash for logging
793 		}
794 	}
795 
796 	/**
797 	 * Forwards a method entry message to the registered trace listeners.
798 	 */
799 	public void entry() {
800 		trace(Level.FINEST, "Entering method");
801 	}
802 
803 	/**
804 	 * Forwards a method exit message to the registered trace listeners.
805 	 */
806 	public void exit() {
807 		trace(Level.FINEST, "Exiting method");
808 	}
809 
810 	/**
811 	 * Returns the output stream to which all CIM-XML traffic (outgoing &amp;
812 	 * incoming) will be copied for debugging purposes.
813 	 *
814 	 * @return The output stream. A <code>null</code> value means that CIM-XML
815 	 *         debugging is disabled
816 	 */
817 	public OutputStream getXmlTraceStream() {
818 		return this.iXmlTraceStream;
819 	}
820 
821 	/**
822 	 * Sets an output stream to which all CIM-XML traffic (outgoing &amp;
823 	 * incoming) will be copied for debugging purposes.
824 	 *
825 	 * @param pStream
826 	 *            The output stream. A <code>null</code> value means that
827 	 *            CIM-XML debugging is disabled.
828 	 */
829 	public void setXmlTraceStream(OutputStream pStream) {
830 		this.iXmlTraceStream = pStream;
831 	}
832 
833 	/*
834 	 * Initializes the CIM-XML trace file (sblim.wbem.cimxmlTraceStream) during
835 	 * initialization of LogAndTraceBroker - the trace file is used if CIM-XML
836 	 * tracing is enabled (sblim.wbem.cimxmlTracing=true) but there is no
837 	 * CIM-XML trace stream set via setXmlTraceStream()
838 	 */
839 	private void initXmlTraceFile() {
840 		try {
841 			if (WBEMConfiguration.getGlobalConfiguration().isCimXmlTracingEnabled()) {
842 				String filename = WBEMConfiguration.getGlobalConfiguration().getCimXmlTraceStream();
843 				if (filename != null && filename.length() > 0 && this.iXmlTraceFile == null && getXmlTraceStream() == null) {
844 					if (filename.equalsIgnoreCase("System.out")) {
845 						this.iXmlTraceFile = System.out;
846 					} else if (filename.equalsIgnoreCase("System.err")) {
847 						this.iXmlTraceFile = System.err;
848 					} else {
849 						try {
850 							this.iXmlTraceFile = new FileOutputStream(filename);
851 						} catch (IOException e) {
852 							trace(
853 								Level.FINE,
854 								"Unable to open " + WBEMConfigurationProperties.CIMXML_TRACE_STREAM + "=" + filename,
855 								e
856 							);
857 						}
858 					}
859 					setXmlTraceStream(this.iXmlTraceFile);
860 				}
861 			}
862 		} catch (Exception e) {
863 			// Don't crash for logging
864 		}
865 	}
866 
867 	/**
868 	 * Analyzes the stack trace and determines from where the
869 	 * <code>LogAndTraceBroker</code> was called.
870 	 *
871 	 * @return First <code>StackTraceElement</code> outside the
872 	 *         <code>LogAndTraceBroker</code>
873 	 */
874 	private StackTraceElement getCaller() {
875 		StackTraceElement[] stack = (new Throwable()).getStackTrace();
876 		for (int i = 0; i < stack.length; ++i) {
877 			StackTraceElement frame = stack[i];
878 			String cname = frame.getClassName();
879 			if (!this.iTHIS_CLASS.equals(cname) && !this.iTHROWABLE.equals(cname)) {
880 				return frame;
881 			}
882 		}
883 		return null;
884 	}
885 
886 	/**
887 	 * Removes all handlers from a logger
888 	 *
889 	 * @param pLogger
890 	 *            The logger
891 	 */
892 	private void removeHandlers(Logger pLogger) {
893 		Handler[] handlers = pLogger.getHandlers();
894 		for (int i = 0; i < handlers.length; ++i) {
895 			pLogger.removeHandler(handlers[i]);
896 			handlers[i].close();
897 		}
898 	}
899 
900 	/**
901 	 * Checks whether there are trace listeners installed that will log a trace
902 	 * message with the specified level. Use this method to determine if a
903 	 * trace() method call could result in logging before preparing the
904 	 * information to be logged. For example:
905 	 *
906 	 * <pre>
907 	 *     if (logger.isLoggableTrace(Level.WARNING) {
908 	 *         // Prepare info for logging
909 	 *         logger.trace(Level.WARNING, ...
910 	 * </pre>
911 	 *
912 	 * @param pLevel
913 	 *            The <code>Level</code> of the trace message.
914 	 * @return <code>true</code> if trace message could be logged,
915 	 *         <code>false</code> otherwise.
916 	 */
917 	public boolean isLoggableTrace(Level pLevel) {
918 		// If there are no trace listeners or specified level is OFF, message
919 		// will not be logged
920 		if (this.iTraceListeners.size() == 0 || pLevel.intValue() == Level.OFF.intValue()) return false;
921 
922 		// If there are external trace listeners, message could be logged (user
923 		// can alter level at will, so we do not know what it is)
924 		if (this.iNumExternalTraceListeners > 0) return true;
925 
926 		// If there are internal trace listeners, determine if message will be
927 		// logged
928 		if (this.iNumInternalTraceListeners > 0) {
929 			int level = Level.OFF.intValue();
930 			if (this.iInternalListenerTraceFile != null) {
931 				level = this.iInternalLevelTraceFile;
932 			}
933 			if (this.iInternalListenerTraceConsole != null && level > this.iInternalLevelConsole) {
934 				level = this.iInternalLevelConsole;
935 			}
936 			return level <= pLevel.intValue();
937 		}
938 
939 		return true;
940 	}
941 
942 	/**
943 	 * Checks whether there are log listeners installed that will log a message
944 	 * with the specified level. Use this method to determine if a message()
945 	 * method call could result in logging before preparing the information to
946 	 * be logged. For example:
947 	 *
948 	 * <pre>
949 	 *     if (logger.isLoggableMessage(Level.WARNING) {
950 	 *         // Prepare info for logging
951 	 *         logger.message(Level.WARNING, ...
952 	 * </pre>
953 	 *
954 	 * @param pLevel
955 	 *            The <code>Level</code> of the message.
956 	 * @return <code>true</code> if message could be logged, <code>false</code>
957 	 *         otherwise.
958 	 */
959 	public boolean isLoggableMessage(Level pLevel) {
960 		// If message is traceable, message could be logged
961 		if (isLoggableTrace(pLevel)) return true;
962 
963 		// If there are no log listeners or specified level is OFF, message
964 		// will not be logged
965 		if (this.iLogListeners.size() == 0 || pLevel.intValue() == Level.OFF.intValue()) return false;
966 
967 		// If there are external log listeners, message could be logged (user
968 		// can alter level at will, so we do not know what it is)
969 		if (this.iNumExternalLogListeners > 0) return true;
970 
971 		// If there are internal log listeners, determine if message will be
972 		// logged
973 		if (this.iNumInternalLogListeners > 0) {
974 			int level = Level.OFF.intValue();
975 			if (this.iInternalListenerLogFile != null) {
976 				level = this.iInternalLevelLogFile;
977 			}
978 			if (this.iInternalListenerLogConsole != null && level > this.iInternalLevelConsole) {
979 				level = this.iInternalLevelConsole;
980 			}
981 			return level <= pLevel.intValue();
982 		}
983 
984 		return true;
985 	}
986 
987 	/**
988 	 * Checks whether there are CIM-XML trace listeners installed that will log
989 	 * a CIM-XML trace message. Use this method to determine if a trace() method
990 	 * call could result in logging before preparing the information to be
991 	 * logged. For example:
992 	 *
993 	 * <pre>
994 	 *     if (logger.isLoggableCIMXMLTrace(Level.FINEST) {
995 	 *         // Prepare info for logging
996 	 *         logger.traceCIMXML(Level.FINEST, ...
997 	 * </pre>
998 	 *
999 	 * @param pLevel
1000 	 *            The <code>Level</code> of the trace message.
1001 	 * @return <code>true</code> if CIM-XML trace message could be logged,
1002 	 *         <code>false</code> otherwise.
1003 	 */
1004 	public boolean isLoggableCIMXMLTrace(Level pLevel) {
1005 		// If there are no CIM-XML trace listeners or specified level is OFF,
1006 		// message will not be logged
1007 		if (this.iCIMXMLTraceListeners.size() == 0 || pLevel.intValue() == Level.OFF.intValue()) return false;
1008 
1009 		return true;
1010 	}
1011 }