/**
* Copyright 2008-2009 Dan Pritchett
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.addsimplicity.anicetus;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Stack;
import org.addsimplicity.anicetus.entity.CompletionStatus;
import org.addsimplicity.anicetus.entity.ExecInfo;
import org.addsimplicity.anicetus.entity.GlobalInfo;
import org.addsimplicity.anicetus.entity.SubTypedInfo;
import org.addsimplicity.anicetus.entity.TelemetryEvent;
import org.addsimplicity.anicetus.entity.TelemetrySession;
import org.addsimplicity.anicetus.entity.TelemetryState;
import org.addsimplicity.anicetus.entity.TelemetryTransaction;
import org.addsimplicity.anicetus.io.DeliveryAdapter;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* The TelemetryContext establishes the execution container for all telemetry.
* The execution context is defined by the system, process, and potentially the
* thread of execution for the session. A session is a logical concept that
* represents a unit of activity performed by the application.
*
* A session provides a container for other telemetry artifacts. Transactions
* are one of the artifacts that are also containers for other artifacts. The
* TelemetryContext provides a convenience method for creating other artifacts
* that will be parented to the current container (either session or
* transaction).
*
* The TelemetryContext is responsible for sending telemetry to the bus. By
* default, a session is sent anytime it ends. Applications may also send beacon
* telemetry (i.e. state and events) directly to the telemetry bus using the
* context convenience method.
*
* The lifecycle of the session is controlled by startSession and endSession.
* These methods are automatically called from the Spring container if Spring is
* used to manage the lifecycle of the context.
*
* @author Dan Pritchett (driveawedge@yahoo.com)
*
*/
public class TelemetryContext implements InitializingBean, DisposableBean {
private final Stack<ExecInfo> m_context = new Stack<ExecInfo>();
private DeliveryAdapter m_deliveryAdapter;
private String m_operationName;
private int m_processIdentifier = -1;
private String m_reportingNode;
private TelemetrySession m_session;
/**
* Called after the Spring framework sets all properties. This method will
* start the session.
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
startSession();
}
/**
* The beginTransaction method starts a new transaction as a child of the
* current session or transaction.
*
* @param resourceId
* The application defined resource identifier.
* @return an initialized transaction.
*/
public TelemetryTransaction beginTransaction(String resourceId) {
final TelemetryTransaction trans = new TelemetryTransaction(m_context.peek());
trans.setResourceId(resourceId);
setReporting(trans);
m_context.push(trans);
return trans;
}
/**
* The destroy method is called by the Spring framework when the context is
* being disposed. An open session will be closed and published before the
* bean is disposed.
*
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
if (m_context.size() > 0) {
endSession();
}
}
/**
* The current session is ended. Any open transactions are closed with their
* completion status set to unknown. The session is published on the telemetry
* bus.
*/
public void endSession() {
closeDanglingTrans();
m_session.complete();
m_context.pop();
m_deliveryAdapter.sendTelemetry(m_session);
startSession();
}
/**
* The current transaction is closed. The completion status will be set to
* unknown if it has not already been set.
*/
public void endTransaction() {
if (m_context.size() <= 1) {
return; // Something is unbalanced but do we throw exceptions in reporting
// flows?
}
final TelemetryTransaction trans = (TelemetryTransaction) m_context.pop();
if (trans.getStatus() == null) {
trans.setStatus(CompletionStatus.Unknown);
}
trans.complete();
}
/**
* Return the currently set delivery adapter.
*
* @return the current delivery adapter.
*/
public DeliveryAdapter getDeliveryAdapter() {
return m_deliveryAdapter;
}
/**
* Return the operation name. The operation name is set on the session when it
* is created.
*
* @return the operation name.
*/
public String getOperationName() {
return m_operationName;
}
/**
* Return the processor identifier. The process identifier is extracted from
* the system property anicetus.processid. The process identifier is used in
* conjunction with the thread identifier to construct the session execution
* context.
*
* @return the processor identifier.
*/
public int getProcessIdentifier() {
return m_processIdentifier;
}
/**
* Return the reporting node. The reporting node is set to the host name of
* the default interface on the system where the application is run. The
* reporting node is set on the session.
*
* @return the reporting node.
*/
public String getReportingNode() {
return m_reportingNode;
}
/**
* Return the current active session.
*
* @return the current active session.
*/
public TelemetrySession getSession() {
return m_session;
}
/**
* Create an event, child of the current context (session or transaction).
*
* @param type
* The application defined type of the event.
* @return the newly created event.
*/
public SubTypedInfo newEvent(String type) {
final SubTypedInfo evt = new TelemetryEvent(m_context.peek());
evt.setType(type);
setReporting(evt);
return evt;
}
/**
* Create a state, child of the current context (session or transaction).
*
* @return the newly created state.
*/
public TelemetryState newState() {
final TelemetryState state = new TelemetryState(m_context.peek());
setReporting(state);
return state;
}
/**
* Return the current execution context (session or transaction).
*
* @return the session or transaction context in effect.
*/
public ExecInfo peekTransaction() {
return m_context.peek();
}
/**
* Pop the current transaction off the top of the stack. The session will not
* be popped from the stack.
*
* @return the current transaction or null if no transactions are left.
*/
public ExecInfo popTransaction() {
return m_context.size() > 1 ? m_context.pop() : null;
}
/**
* Push a transaction to the top of the execution context. The standard
* reporting information will be added to the transaction.
*
* @param transaction
* The transaction to push.
*/
public void pushTransaction(ExecInfo transaction) {
setReporting(transaction);
m_context.push(transaction);
}
/**
* Send a telemetry beacon that is independent of the current execution
* context. The beacon will be sent immediately. The basic information about
* the current application environment will be set.
*
* @param beacon
* The beacon to be sent.
*/
public void sendBeacon(GlobalInfo beacon) {
fillBaseInfo(beacon);
m_deliveryAdapter.sendTelemetry(beacon);
}
/**
* Set the delivery adpater that will be used to publish events on the
* telemetry bus.
*
* @param deliveryAdapter
* The delivery adapter instance.
*/
public void setDeliveryAdapter(DeliveryAdapter deliveryAdapter) {
m_deliveryAdapter = deliveryAdapter;
}
/**
* Set the operation name for this context. This will be set on sessions when
* they are created.
*
* @param operationName
*/
public void setOperationName(String operationName) {
m_operationName = operationName;
}
/**
* Set the process identifier for this context. The process identifier should
* be the operating system identifier for the JVM process.
*
* @param processIdentifier
* The JVM process identifier.
*/
public void setProcessIdentifier(int processIdentifier) {
m_processIdentifier = processIdentifier;
}
/**
* Set the node identifier for this context. This is typically the host name
* or IP address.
*
* @param reportingNode
* The node network identifier.
*/
public void setReportingNode(String reportingNode) {
m_reportingNode = reportingNode;
}
/**
* Start a new session. The session is filled with the current execution
* environment information.
*/
public void startSession() {
m_session = createSession();
m_context.clear();
m_context.push(m_session);
m_session.setOperationName(m_operationName);
sniffHost();
m_session.setReportingNode(m_reportingNode);
sniffProcessId();
m_session.setExecutionContext(makeExecContext());
}
private void closeDanglingTrans() {
while (m_context.size() > 1) {
endTransaction();
}
}
private void fillBaseInfo(GlobalInfo info) {
if (info.getReportingNode() == null) {
info.setReportingNode(m_reportingNode);
}
if (info.getExecutionContext() == null) {
info.setExecutionContext(makeExecContext());
}
}
private String makeExecContext() {
return (m_processIdentifier >= 0 ? m_processIdentifier : "UNKNOWN") + "." + Thread.currentThread().getId();
}
private void setReporting(GlobalInfo target) {
target.setReportingNode(m_context.peek().getReportingNode());
}
private void sniffHost() {
if (m_reportingNode == null) {
try {
final InetAddress local = InetAddress.getLocalHost();
m_reportingNode = local.getCanonicalHostName();
}
catch (final UnknownHostException e) {
// Not sure what else to do. Just local host it.
//
m_reportingNode = "127.0.0.1";
}
}
}
private void sniffProcessId() {
if (m_processIdentifier < 0) {
final String pid = System.getProperty("anicetus.processid");
if (pid != null) {
try {
m_processIdentifier = Integer.parseInt(pid);
}
catch (final NumberFormatException e) {
// Set it to zero. This will cause something other than 'UNKNOWN'
// which is a hint
// they set the property to an unparseable string.
//
m_processIdentifier = 0;
}
}
}
}
protected TelemetrySession createSession() {
return new TelemetrySession();
}
}