blob: 9c464d0e379a185aa2ee5cde14a84b137020c24d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2009 Tasktop Technologies 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:
* Tasktop Technologies - initial API and implementation
* Ken Sueda - XML serialization
*******************************************************************************/
package org.eclipse.mylyn.internal.monitor.usage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.net.HtmlStreamTokenizer;
import org.eclipse.mylyn.commons.net.HtmlStreamTokenizer.Token;
import org.eclipse.mylyn.internal.context.core.InteractionContextExternalizer;
import org.eclipse.mylyn.monitor.core.AbstractMonitorLog;
import org.eclipse.mylyn.monitor.core.IInteractionEventListener;
import org.eclipse.mylyn.monitor.core.InteractionEvent;
import org.eclipse.mylyn.monitor.core.InteractionEvent.Kind;
/**
* @author Mik Kersten TODO: use buffered output stream for better performance?
*/
public class InteractionEventLogger extends AbstractMonitorLog implements IInteractionEventListener {
private int eventAccumulartor = 0;
private final List<InteractionEvent> queue = new CopyOnWriteArrayList<InteractionEvent>();
private final InteractionEventObfuscator handleObfuscator = new InteractionEventObfuscator();
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S z", Locale.ENGLISH); //$NON-NLS-1$
public InteractionEventLogger(File outputFile) {
this.outputFile = outputFile;
}
public synchronized void interactionObserved(InteractionEvent event) {
// System.err.println("> " + event); //$NON-NLS-1$
if (UiUsageMonitorPlugin.getDefault() == null) {
StatusHandler.log(new Status(IStatus.WARNING, UiUsageMonitorPlugin.ID_PLUGIN,
"Attempted to log event before usage monitor start")); //$NON-NLS-1$
}
if (UiUsageMonitorPlugin.getDefault().isObfuscationEnabled()) {
String obfuscatedHandle = handleObfuscator.obfuscateHandle(event.getStructureKind(),
event.getStructureHandle());
event = new InteractionEvent(event.getKind(), event.getStructureKind(), obfuscatedHandle,
event.getOriginId(), event.getNavigation(), event.getDelta(), event.getInterestContribution());
}
try {
if (started) {
String xml = getXmlForEvent(event);
if (outputStream != null) {
outputStream.write(xml.getBytes());
}
} else if (event != null) {
queue.add(event);
}
eventAccumulartor++;
} catch (Throwable t) {
StatusHandler.log(new Status(IStatus.WARNING, UiUsageMonitorPlugin.ID_PLUGIN,
"Could not log interaction event", t)); //$NON-NLS-1$
}
}
@Override
public void startMonitoring() {
super.startMonitoring();
for (InteractionEvent queuedEvent : queue) {
interactionObserved(queuedEvent);
}
queue.clear();
}
@Override
public void stopMonitoring() {
super.stopMonitoring();
if (UiUsageMonitorPlugin.getDefault() != null) {
UiUsageMonitorPlugin.getDefault().incrementObservedEvents(eventAccumulartor);
}
eventAccumulartor = 0;
}
private String getXmlForEvent(InteractionEvent event) {
return writeLegacyEvent(event);
}
/**
* @return true if successfully cleared
*/
public synchronized void clearInteractionHistory() throws IOException {
this.clearInteractionHistory(true);
}
public synchronized void clearInteractionHistory(boolean startMonitoring) throws IOException {
stopMonitoring();
outputStream = new FileOutputStream(outputFile, false);
outputStream.flush();
outputStream.close();
outputFile.delete();
outputFile.createNewFile();
if (startMonitoring) {
startMonitoring();
}
}
public List<InteractionEvent> getHistoryFromFile(File file) {
return getHistoryFromFile(file, new NullProgressMonitor());
}
public List<InteractionEvent> getHistoryFromFile(File file, IProgressMonitor monitor) {
List<InteractionEvent> events = new ArrayList<InteractionEvent>();
InputStream inputStream = null;
long fileLength = 0;
ZipFile zip = null;
try {
// The file may be a zip file...
if (file.getName().endsWith(".zip")) { //$NON-NLS-1$
zip = new ZipFile(file);
if (zip.entries().hasMoreElements()) {
ZipEntry entry = zip.entries().nextElement();
inputStream = zip.getInputStream(entry);
fileLength = entry.getSize();
}
} else {
inputStream = new FileInputStream(file);
fileLength = file.length();
}
//450: the approximate size of an event in XML
int numberOfEventsEstimate = (int) (fileLength / 450);
monitor.beginTask(Messages.InteractionEventLogger_Reading_History_From_File, numberOfEventsEstimate);
getHistoryFromStream(inputStream, events, monitor);
} catch (Exception e) {
StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN,
"Could not read interaction history", e)); //$NON-NLS-1$
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN,
"unable to close input stream", e)); //$NON-NLS-1$
}
}
if (zip != null) {
try {
zip.close();
} catch (IOException e) {
// ignore
}
}
}
monitor.done();
return events;
}
/**
* @param events
* @param monitor
* @param tag
* @param endl
* @param buf
*/
private void getHistoryFromStream(InputStream reader, List<InteractionEvent> events, IProgressMonitor monitor)
throws IOException {
String xml;
int index;
String buf = ""; //$NON-NLS-1$
String tag = "</" + InteractionContextExternalizer.ELMNT_INTERACTION_HISTORY_OLD + ">"; //$NON-NLS-1$ //$NON-NLS-2$
String endl = "\r\n"; //$NON-NLS-1$
byte[] buffer = new byte[1000];
int bytesRead = 0;
while ((bytesRead = reader.read(buffer)) != -1) {
buf = buf + new String(buffer, 0, bytesRead);
while ((index = buf.indexOf(tag)) != -1) {
index += tag.length();
xml = buf.substring(0, index);
InteractionEvent event = readLegacyEvent(xml);
if (event != null) {
events.add(event);
}
if (index + endl.length() > buf.length()) {
buf = ""; //$NON-NLS-1$
} else {
buf = buf.substring(index + endl.length(), buf.length());
}
monitor.worked(1);
}
buffer = new byte[1000];
}
}
private static final String OPEN = "<"; //$NON-NLS-1$
private static final String CLOSE = ">"; //$NON-NLS-1$
private static final String SLASH = "/"; //$NON-NLS-1$
private static final String ENDL = "\n"; //$NON-NLS-1$
private static final String TAB = "\t"; //$NON-NLS-1$
@Deprecated
public String writeLegacyEvent(InteractionEvent e) {
try {
StringBuffer res = new StringBuffer();
String tag = "interactionEvent"; //$NON-NLS-1$
res.append(OPEN);
res.append(tag);
res.append(CLOSE);
res.append(ENDL);
openElement(res, "kind"); //$NON-NLS-1$
formatContent(res, e.getKind());
closeElement(res, "kind"); //$NON-NLS-1$
openElement(res, "date"); //$NON-NLS-1$
formatContent(res, e.getDate());
closeElement(res, "date"); //$NON-NLS-1$
openElement(res, "endDate"); //$NON-NLS-1$
formatContent(res, e.getEndDate());
closeElement(res, "endDate"); //$NON-NLS-1$
openElement(res, "originId"); //$NON-NLS-1$
formatContent(res, e.getOriginId());
closeElement(res, "originId"); //$NON-NLS-1$
openElement(res, "structureKind"); //$NON-NLS-1$
formatContent(res, e.getStructureKind());
closeElement(res, "structureKind"); //$NON-NLS-1$
openElement(res, "structureHandle"); //$NON-NLS-1$
formatContent(res, e.getStructureHandle());
closeElement(res, "structureHandle"); //$NON-NLS-1$
openElement(res, "navigation"); //$NON-NLS-1$
formatContent(res, e.getNavigation());
closeElement(res, "navigation"); //$NON-NLS-1$
openElement(res, "delta"); //$NON-NLS-1$
formatContent(res, e.getDelta());
closeElement(res, "delta"); //$NON-NLS-1$
openElement(res, "interestContribution"); //$NON-NLS-1$
formatContent(res, e.getInterestContribution());
closeElement(res, "interestContribution"); //$NON-NLS-1$
res.append(OPEN);
res.append(SLASH);
res.append(tag);
res.append(CLOSE);
res.append(ENDL);
return res.toString();
} catch (Throwable t) {
StatusHandler.log(new Status(IStatus.ERROR, UiUsageMonitorPlugin.ID_PLUGIN, "Could not write event", t)); //$NON-NLS-1$
return ""; //$NON-NLS-1$
}
}
private void formatContent(StringBuffer buffer, float interestContribution) {
buffer.append(interestContribution);
}
@SuppressWarnings("deprecation")
private void formatContent(StringBuffer buffer, String content) {
if (content != null && content.length() > 0) {
String xmlContent;
xmlContent = org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(content);
xmlContent = xmlContent.replace("\n", "\n\t\t"); //$NON-NLS-1$ //$NON-NLS-2$
buffer.append(xmlContent);
}
}
private void formatContent(StringBuffer buffer, Kind kind) {
buffer.append(kind.toString());
}
private void formatContent(StringBuffer buffer, Date date) {
buffer.append(dateFormat.format(date));
}
private void openElement(StringBuffer buffer, String tag) {
buffer.append(TAB);
buffer.append(OPEN);
buffer.append(tag);
buffer.append(CLOSE);
}
private void closeElement(StringBuffer buffer, String tag) {
buffer.append(OPEN);
buffer.append(SLASH);
buffer.append(tag);
buffer.append(CLOSE);
buffer.append(ENDL);
}
public InteractionEvent readLegacyEvent(String xml) {
Reader reader = new StringReader(xml);
HtmlStreamTokenizer tokenizer = new HtmlStreamTokenizer(reader, null);
String kind = ""; //$NON-NLS-1$
String startDate = ""; //$NON-NLS-1$
String endDate = ""; //$NON-NLS-1$
String originId = ""; //$NON-NLS-1$
String structureKind = ""; //$NON-NLS-1$
String structureHandle = ""; //$NON-NLS-1$
String navigation = ""; //$NON-NLS-1$
String delta = ""; //$NON-NLS-1$
String interest = ""; //$NON-NLS-1$
try {
for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) {
if (token.getValue().toString().equals("<kind>")) { //$NON-NLS-1$
kind = readStringContent(tokenizer, "</kind>"); //$NON-NLS-1$
kind = kind.toLowerCase(Locale.ENGLISH);
} else if (token.getValue().toString().equals("<date>")) { //$NON-NLS-1$
startDate = readStringContent(tokenizer, "</date>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<endDate>")) { //$NON-NLS-1$
endDate = readStringContent(tokenizer, "</endDate>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<originId>")) { //$NON-NLS-1$
originId = readStringContent(tokenizer, "</originId>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<structureKind>")) { //$NON-NLS-1$
structureKind = readStringContent(tokenizer, "</structureKind>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<structureHandle>")) { //$NON-NLS-1$
structureHandle = readStringContent(tokenizer, "</structureHandle>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<navigation>")) { //$NON-NLS-1$
navigation = readStringContent(tokenizer, "</navigation>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<delta>")) { //$NON-NLS-1$
delta = readStringContent(tokenizer, "</delta>"); //$NON-NLS-1$
} else if (token.getValue().toString().equals("<interestContribution>")) { //$NON-NLS-1$
interest = readStringContent(tokenizer, "</interestContribution>"); //$NON-NLS-1$
}
}
float interestFloatVal = 0;
try {
interestFloatVal = Float.parseFloat(interest);
} catch (NumberFormatException nfe) {
// ignore for empty interest values
}
InteractionEvent event = new InteractionEvent(Kind.fromString(kind), structureKind, structureHandle,
originId, navigation, delta, interestFloatVal, dateFormat.parse(startDate),
dateFormat.parse(endDate));
return event;
} catch (ParseException e) {
System.err.println("readevent: " + xml); //$NON-NLS-1$
e.printStackTrace();
} catch (IOException e) {
System.err.println("readevent: " + xml); //$NON-NLS-1$
e.printStackTrace();
} catch (Exception e) {
System.err.println("readevent: " + xml); //$NON-NLS-1$
e.printStackTrace();
}
return null;
}
@SuppressWarnings("deprecation")
private String readStringContent(HtmlStreamTokenizer tokenizer, String endTag) throws IOException, ParseException {
StringBuffer content = new StringBuffer();
Token token = tokenizer.nextToken();
while (!token.getValue().toString().equals(endTag)) {
if (content.length() > 0) {
content.append(' ');
}
content.append(token.getValue().toString());
token = tokenizer.nextToken();
}
return org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertXmlToString(content.toString()).trim();
}
public static DateFormat dateFormat() {
return (DateFormat) dateFormat.clone();
}
}