/******************************************************************************* * Copyright (c) 2011, 2014 Wind River Systems, Inc. and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tcf.log.core.internal.listener; import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.osgi.util.NLS; import org.eclipse.tcf.core.AbstractChannel; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.te.runtime.concurrent.util.ExecutorsUtil; import org.eclipse.tcf.te.tcf.log.core.activator.CoreBundleActivator; import org.eclipse.tcf.te.tcf.log.core.events.MonitorEvent; import org.eclipse.tcf.te.tcf.log.core.interfaces.IPreferenceKeys; import org.eclipse.tcf.te.tcf.log.core.interfaces.ITracing; import org.eclipse.tcf.te.tcf.log.core.internal.nls.Messages; import org.eclipse.tcf.te.tcf.log.core.manager.LogManager; /** * TCF logging channel trace listener manager implementation. */ public final class ChannelTraceListenerManager { /** * Time format representing date and time with milliseconds. */ public final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //$NON-NLS-1$ // The map of trace listeners per channel private final Map<IChannel, AbstractChannel.TraceListener> listeners = new HashMap<IChannel, AbstractChannel.TraceListener>(); // The map of queued messaged per channel /* default */ final Map<IChannel, List<String>> queued = new HashMap<IChannel, List<String>>(); /* * Thread save singleton instance creation. */ private static class LazyInstanceHolder { public static ChannelTraceListenerManager instance = new ChannelTraceListenerManager(); } /** * Returns the singleton instance for the manager. */ public static ChannelTraceListenerManager getInstance() { return LazyInstanceHolder.instance; } /** * Constructor. */ /* default */ ChannelTraceListenerManager() { } /** * New channel opened. Attach a channel trace listener. * * @param logname The log name or <code>null</code>. * @param channel The channel. Must not be <code>null</code>. * @param message A message or <code>null</code>. */ public void onChannelOpened(final String logname, final IChannel channel, final String message) { Assert.isNotNull(channel); Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ // The trace listener interface does not have a onChannelOpenend method, but // for consistency, log the channel opening similar to the others. if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) { CoreBundleActivator.getTraceHandler().trace("TraceListener.onChannelOpened ( " + channel + ", \"" + message + "\" )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this); } // The trace listeners can be accessed only via AbstractChannel if (!(channel instanceof AbstractChannel)) return; // Determine the remote peer from the channel final IPeer peer = channel.getRemotePeer(); if (peer == null) return; // Get the preference key if or if not logging is enabled boolean loggingEnabled = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_LOGGING_ENABLED); // If false, we are done here and wont create any console or trace listener. if (!loggingEnabled) return; // As the channel has just opened, there should be no trace listener, but better be safe and check AbstractChannel.TraceListener traceListener = listeners.remove(channel); if (traceListener != null) ((AbstractChannel)channel).removeTraceListener(traceListener); // Create a new trace listener instance traceListener = new ChannelTraceListener(logname, channel); // Attach trace listener to the channel ((AbstractChannel)channel).addTraceListener(traceListener); // Remember the associated trace listener listeners.put(channel, traceListener); // Determine the date and time of the message before spawning to the log thread. final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis())); // This method is called in the TCF event dispatch thread. There // is no need that the logging itself keeps the TCF event dispatch // thread busy. Execute the logging itself in a separate thread but // still maintain the order of the messages. ExecutorsUtil.execute(new Runnable() { @Override public void run() { String fullMessage = NLS.bind(Messages.ChannelTraceListener_channelOpened_message, new Object[] { date, Integer.toHexString(channel.hashCode()), message != null ? "(" + message.trim() + ")" : "" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ }); // Get the file writer FileWriter writer = LogManager.getInstance().getWriter(logname, peer); if (writer != null) { try { writer.write("\n\n\n"); //$NON-NLS-1$ // Get the queued messages List<String> queue = queued.remove(channel); // Write the queued messages if (queue != null) { for (String m : queue) { writer.write(m); writer.write("\n"); //$NON-NLS-1$ } } // Write the opened message writer.write(fullMessage); writer.write("\n"); //$NON-NLS-1$ writer.flush(); } catch (IOException e) { /* ignored on purpose */ } } LogManager.getInstance().monitor(peer, MonitorEvent.Type.OPEN, new MonitorEvent.Message('F', fullMessage)); } }); } /** * Channel is opening. * <p> * This is the state where {@link IPeer#openChannel()} got called but no * further redirect or channel listener got invoked. * * @param logname The log name or <code>null</code>. * @param channel The channel. Must not be <code>null</code>. * @param message A message or <code>null</code>. */ public void onChannelOpening(final String logname, final IChannel channel, final String message) { Assert.isNotNull(channel); Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) { CoreBundleActivator.getTraceHandler().trace("TraceListener.onChannelOpening ( " + channel + ", \"" + message + "\" )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this); } // Get the preference key if or if not logging is enabled boolean loggingEnabled = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_LOGGING_ENABLED); // If false, we are done here and wont create any console or trace listener. if (!loggingEnabled) return; // Determine the date and time of the message before spawning to the log thread. final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis())); // This method is called in the TCF event dispatch thread. There // is no need that the logging itself keeps the TCF event dispatch // thread busy. Execute the logging itself in a separate thread but // still maintain the order of the messages. ExecutorsUtil.execute(new Runnable() { @Override public void run() { String fullMessage = NLS.bind(Messages.ChannelTraceListener_channelOpening_message, new Object[] { date, Integer.toHexString(channel.hashCode()), message != null ? "(" + message.trim() + ")" : "" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ }); List<String> queue = queued.get(channel); if (queue == null) { queue = new ArrayList<String>(); queued.put(channel, queue); } queue.add(fullMessage); } }); } /** * Channel got redirected. * * @param logname The log name or <code>null</code>. * @param channel The channel. Must not be <code>null</code>. * @param message A message or <code>null</code>. */ public void onChannelRedirected(final String logname, final IChannel channel, final String message) { Assert.isNotNull(channel); Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) { CoreBundleActivator.getTraceHandler().trace("TraceListener.onChannelRedirected ( " + channel + ", \"" + message + "\" )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this); } // Get the preference key if or if not logging is enabled boolean loggingEnabled = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_LOGGING_ENABLED); // If false, we are done here and wont create any console or trace listener. if (!loggingEnabled) return; // Determine the date and time of the message before spawning to the log thread. final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis())); // This method is called in the TCF event dispatch thread. There // is no need that the logging itself keeps the TCF event dispatch // thread busy. Execute the logging itself in a separate thread but // still maintain the order of the messages. ExecutorsUtil.execute(new Runnable() { @Override public void run() { String fullMessage = NLS.bind(Messages.ChannelTraceListener_channelRedirected_message, new Object[] { date, Integer.toHexString(channel.hashCode()), message != null ? "(" + message.trim() + ")" : "" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ }); List<String> queue = queued.get(channel); if (queue == null) { queue = new ArrayList<String>(); queued.put(channel, queue); } queue.add(fullMessage); } }); } /** * Channel remote services got queried. * * @param logname The log name or <code>null</code>. * @param channel The channel. Must not be <code>null</code>. * @param message A message or <code>null</code>. */ public void onChannelServices(final String logname, final IChannel channel, final String message) { Assert.isNotNull(channel); Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) { CoreBundleActivator.getTraceHandler().trace("TraceListener.onChannelServices ( " + channel + ", \"" + message + "\" )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this); } // Get the preference key if or if not logging is enabled boolean loggingEnabled = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_LOGGING_ENABLED); // If false, we are done here and wont create any console or trace listener. if (!loggingEnabled) return; // Determine the date and time of the message before spawning to the log thread. final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis())); // This method is called in the TCF event dispatch thread. There // is no need that the logging itself keeps the TCF event dispatch // thread busy. Execute the logging itself in a separate thread but // still maintain the order of the messages. ExecutorsUtil.execute(new Runnable() { @Override public void run() { String fullMessage = NLS.bind(Messages.ChannelTraceListener_channelServices_message, new Object[] { date, Integer.toHexString(channel.hashCode()), message != null ? "(" + message.trim() + ")" : "" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ }); List<String> queue = queued.get(channel); if (queue == null) { queue = new ArrayList<String>(); queued.put(channel, queue); } queue.add(fullMessage); } }); } /** * Mark an event in the channel log. * * @param logname The log name or <code>null</code>. * @param channel The channel. Must not be <code>null</code>. * @param message A message or <code>null</code>. */ public void onMark(final String logname, final IChannel channel, final String message) { Assert.isNotNull(channel); Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ // The trace listener interface does not have a onChannelOpenend method, but // for consistency, log the channel opening similar to the others. if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) { CoreBundleActivator.getTraceHandler().trace("TraceListener.onMark ( " + channel + ", \"" + message + "\" )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this); } // Determine the remote peer from the channel final IPeer peer = channel.getRemotePeer(); if (peer == null) return; // Get the preference key if or if not logging is enabled boolean loggingEnabled = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_LOGGING_ENABLED); // If false, we are done here and wont create any console or trace listener. if (!loggingEnabled) return; // Determine the date and time of the message before spawning to the log thread. final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis())); // This method is called in the TCF event dispatch thread. There // is no need that the logging itself keeps the TCF event dispatch // thread busy. Execute the logging itself in a separate thread but // still maintain the order of the messages. ExecutorsUtil.execute(new Runnable() { @Override public void run() { String fullMessage = NLS.bind(Messages.ChannelTraceListener_channelMark_message, new Object[] { date, Integer.toHexString(channel.hashCode()), message != null ? "(" + message.trim() + ")" : "" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ }); // Get the file writer FileWriter writer = LogManager.getInstance().getWriter(logname, peer); if (writer != null) { try { // Write the message writer.write(fullMessage); writer.write("\n"); //$NON-NLS-1$ writer.flush(); } catch (IOException e) { /* ignored on purpose */ } } } }); } /** * Channel closed. Detach the channel trace listener if any. * * @param logname The log name or <code>null</code>. * @param channel The channel. Must not be <code>null</code>. */ public void onChannelClosed(String logname, final IChannel channel) { Assert.isNotNull(channel); Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ ExecutorsUtil.execute(new Runnable() { @Override public void run() { // Remove the queued messages queued.remove(channel); } }); // The trace listeners can be accessed only via AbstractChannel if (!(channel instanceof AbstractChannel)) return; // Remove the trace listener if any final AbstractChannel.TraceListener traceListener = listeners.remove(channel); if (traceListener != null) { // Removal needs to happen asynchronous is another dispatch cycle, // otherwise the closed event is not logged. Protocol.invokeLater(new Runnable() { @Override public void run() { ((AbstractChannel)channel).removeTraceListener(traceListener); } }); } } }