001 /* 002 * Copyright 2008-2009 the original author or authors. 003 * The contents of this file are subject to the Mozilla Public License 004 * Version 1.1 (the "License"); you may not use this file except in 005 * compliance with the License. You may obtain a copy of the License at 006 * http://www.mozilla.org/MPL/ 007 * 008 * Software distributed under the License is distributed on an "AS IS" 009 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 010 * License for the specific language governing rights and limitations 011 * under the License. 012 */ 013 014 package com.mtgi.analytics; 015 016 import java.io.Serializable; 017 import java.util.Date; 018 019 import org.apache.commons.logging.Log; 020 import org.apache.commons.logging.LogFactory; 021 022 /** 023 * <i>Applications should generally not have to interact with BehaviorEvent directly.</i> 024 * 025 * Represents an action taken directly or indirectly by a user. 026 * Each action is given a {@link #getType()}, {@link #getName()}, 027 * and {@link #getApplication() application}; 028 * has a {@link #getStart() start time} and {@link #getDuration()}; 029 * and is associated with a {@link #getUserId() user} and {@link #getSessionId() session id} 030 * representing the user's current authenticated session with the application. 031 * Other data like parameter values can be stored in a semi-structured fashion 032 * in {@link #getData()}. Generally the structure of the event data and 033 * the meaning of the {@link #getName()} should depend on the {@link #getType() type}. 034 * BehaviorEvents may be composed of smaller child events, which themselves 035 * may be composed of child events, and so on; an event's children are 036 * accessed with {@link #getChildren()}. 037 * 038 * @see BehaviorEventManager 039 */ 040 public class BehaviorEvent implements Serializable { 041 042 private static final long serialVersionUID = -5341143588240860983L; 043 private static final Log log = LogFactory.getLog(BehaviorEvent.class); 044 045 private Serializable id; 046 private BehaviorEvent parent; 047 private String type; 048 private String name; 049 private String application; 050 private String userId; 051 private String sessionId; 052 private Date start; 053 private Long duration; 054 private EventDataElement data = DeferredDataElement.INSTANCE; 055 private String error; 056 057 protected BehaviorEvent(BehaviorEvent parent, String type, String name, String application, String userId, String sessionId) { 058 this.parent = parent; 059 this.type = type; 060 this.name = name; 061 this.application = application; 062 this.userId = userId; 063 this.sessionId = sessionId; 064 } 065 066 @Override 067 protected void finalize() { 068 if (isStarted() && !isEnded()) 069 log.warn("Event [" + type + ":" + name + ":" + id + "] was started and then discarded!"); 070 } 071 072 /** 073 * Notification that this event has started. {@link #getStart()} 074 * will return the time at which the event began. 075 * 076 * <i>This method should never be called directly by application code.</i> 077 * @see BehaviorTrackingManager#start(BehaviorEvent) 078 * @throws IllegalStateException if this event is already finished or started 079 */ 080 protected void start() { 081 if (isStarted()) 082 throw new IllegalStateException("Event has already started"); 083 start = new Date(); 084 } 085 086 /** 087 * Notification that this event is finished. {@link #getDuration()} 088 * will return the time elapsed since {@link #start()} was called. 089 * 090 * <i>This method should never be called directly by application code.</i> 091 * @see BehaviorTrackingManager#stop(BehaviorEvent) 092 * @throws IllegalStateException if this event is already finished or was never started 093 */ 094 protected void stop() { 095 if (!isStarted()) 096 throw new IllegalStateException("Event has not started"); 097 if (isEnded()) 098 throw new IllegalStateException("Event has already ended"); 099 duration = System.currentTimeMillis() - start.getTime(); 100 } 101 102 /** @return true if this event has started already */ 103 protected boolean isStarted() { 104 return start != null; 105 } 106 107 /** @return true if this event is finished */ 108 protected boolean isEnded() { 109 return duration != null; 110 } 111 112 /** @return true if this event is not nested in some other event */ 113 protected boolean isRoot() { 114 return parent == null; 115 } 116 117 /** 118 * Get the name of the application in which this event took place. 119 */ 120 public String getApplication() { 121 return application; 122 } 123 124 /** 125 * If this event has extra data, it can be accessed here. Otherwise returns 126 * null. 127 * @see #addData() 128 */ 129 public EventDataElement getData() { 130 return data; 131 } 132 133 /** 134 * If this event is finished, return its duration. Otherwise return null. 135 */ 136 public Long getDuration() { 137 return duration; 138 } 139 140 /** If this event ended in error, return a description of that error (null otherwise) */ 141 public String getError() { 142 return error; 143 } 144 public void setError(Throwable t) { 145 setError(t.getClass().getName() + ": " + t.getMessage()); 146 } 147 148 public void setError(String error) { 149 this.error = error; 150 } 151 152 /** a unique identifier (e.g. primary key) for this event */ 153 public Serializable getId() { 154 return id; 155 } 156 public void setId(Serializable id) { 157 this.id = id; 158 } 159 160 /** 161 * Get the name of the event; for example, a method name for 162 * instrumented method calls, or a server request path for 163 * an instrumented servlet. 164 */ 165 public String getName() { 166 return name; 167 } 168 169 /** 170 * If this event is a child of a larger composite event, 171 * return the event's parent. Otherwise returns null. 172 */ 173 public BehaviorEvent getParent() { 174 return parent; 175 } 176 177 /** 178 * If this event is part of an authenticated session, return an 179 * identifier of that session. 180 */ 181 public String getSessionId() { 182 return sessionId; 183 } 184 185 /** 186 * If this event has started, return the date at which the event began. 187 * Otherwise return null. 188 */ 189 public Date getStart() { 190 return start; 191 } 192 193 /** 194 * Get the type of this event. Many applications will only use 195 * one type of event (e.g. "method"), though some may define several. 196 * A single application should generally have only a smaller number of 197 * event types, corresponding to the layer of the system at which the 198 * event originated (e.g. "method", "rendering", "database"). It is 199 * suggested that the value of "type" should give some meaning to how 200 * the value of {@link #getName()} should be interpreted. For example, 201 * an event type of "request" could signify that {@link #getName()} returns 202 * an HTTP request URL. Generally though type/name schemes are up to the application. 203 */ 204 public String getType() { 205 return type; 206 } 207 208 /** 209 * Get the user on whose behalf this event is executing. 210 */ 211 public String getUserId() { 212 return userId; 213 } 214 215 /** 216 * Add metadata to this event, if metadata has not already been added. 217 * @return A container for event data. Calling this method more than once always returns the same instance. 218 */ 219 public EventDataElement addData() { 220 return data.initialize(this); 221 } 222 223 @Override 224 public String toString() { 225 StringBuffer buf = new StringBuffer(); 226 227 buf.append("behavior-event:").append(" id=\"").append(id).append('"'); 228 if (parent != null) 229 buf.append(" parent-id=\"").append(parent.getId()).append('"'); 230 buf.append(" type=\"").append(type).append('"') 231 .append(" name=\"").append(name).append('"') 232 .append(" application=\"").append(application).append('"') 233 .append(" start=\"").append(start).append('"') 234 .append(" duration-ms=\"").append(duration).append('"') 235 .append(" user-id=\"").append(userId).append('"') 236 .append(" session-id=\"").append(sessionId).append('"') 237 .append(" error=\"").append(error).append('"'); 238 239 return buf.toString(); 240 } 241 242 /** 243 * a singleton empty data element, for events that do not need to define any data. 244 * Replaces itself with a new, standard EventDataElement instance on the first call 245 * to {@link #initialize(BehaviorEvent)}. This somewhat arcane construction is an optimization 246 * for calls to {@link #addData}, so that they do not require a null check. 247 */ 248 private static class DeferredDataElement extends ImmutableEventDataElement { 249 private static final long serialVersionUID = -4571142241188203441L; 250 private static final DeferredDataElement INSTANCE = new DeferredDataElement(); 251 private DeferredDataElement() { 252 super("event-data"); 253 } 254 255 @Override 256 public boolean isNull() { 257 return true; 258 } 259 260 @Override 261 protected EventDataElement initialize(BehaviorEvent event) { 262 return event.data = new EventDataElement("event-data"); 263 } 264 } 265 }