/**
* 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.hibernate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.addsimplicity.anicetus.entity.EntityTypeRegistry;
import org.addsimplicity.anicetus.entity.ExecInfo;
import org.addsimplicity.anicetus.entity.GlobalInfo;
/**
* The hibernate telemetry artifact tracks the actions performed by an
* application against the database through the Hibernate framework. The state
* captured provides information about what entities are involved, what
* operations were performed, what tables were involved, and what sql was
* executed. It also tracks timing information about the Hibernate transaction.
*
* @author Dan Pritchett (driveawedge@yahoo.com)
*
*/
public class HibernateTelemetry extends ExecInfo {
static {
EntityTypeRegistry.addSearchPackage(HibernateTelemetry.class.getPackage().getName());
EntityTypeRegistry.addBeanType(HibernateEntity.class);
EntityTypeRegistry.addClassShortName(HibernateTelemetry.class, "HT");
}
/**
* Construct a telemetry artifact without any parent.
*/
public HibernateTelemetry() {
}
/**
* Construct a telemetry artifact with the specified parent.
*
* @param parent
* The parent of the artifact.
*/
public HibernateTelemetry(GlobalInfo parent) {
super(parent);
}
/**
* Add an entity to this artifact. The same entity may appear multiple times,
* once for each operation that is performed upon it. This is true even if the
* same operation is performed more than once on the entity.
*
* @param entity
* The entity to add to the telemetry.
*/
@SuppressWarnings("unchecked")
public void addHibernateEntity(HibernateEntity entity) {
List<HibernateEntity> entities = (List<HibernateEntity>) get(HibernateTelemetryFields.HibernateEntity.name());
if (entities == null) {
entities = new ArrayList<HibernateEntity>();
put(HibernateTelemetryFields.HibernateEntity.name(), entities);
}
entities.add(entity);
}
/**
* Add a SQL statement to the telemetry. SQL statements are generated by
* Hibernate and loosely correlate to the entities that are added. Multiple
* SQL statements may appear for each entity operation however.
*
* This method will also parse the table name from the statement and add them
* to the table lists.
*
* @param statement
* The SQL statement generated by Hibernate.
*/
@SuppressWarnings("unchecked")
public void addSQLStatement(String statement) {
List<String> stmts = (List<String>) get(HibernateTelemetryFields.SQLStatement.name());
if (stmts == null) {
stmts = new ArrayList<String>();
put(HibernateTelemetryFields.SQLStatement.name(), stmts);
}
stmts.add(statement);
String tables[] = getTableFromSQL(statement);
if (tables != null) {
for (String t : tables) {
addTable(t);
}
}
}
/**
* Add a referenced table to the telemetry. Tables are added for any SQL
* statement they appear in.
*
* @param table
* The table name.
*/
@SuppressWarnings("unchecked")
public void addTable(String table) {
Set<String> tables = (Set<String>) get(HibernateTelemetryFields.Table.name());
if (tables == null) {
tables = new HashSet<String>();
;
put(HibernateTelemetryFields.Table.name(), tables);
}
tables.add(table);
}
/**
* Return an immutable collection of entities that were involved in the
* Hibernate transaction.
*
* @return a collection of entities.
*/
@SuppressWarnings("unchecked")
public Collection<HibernateEntity> getHibernateEntities() {
return Collections
.unmodifiableCollection((Collection<? extends HibernateEntity>) get(HibernateTelemetryFields.HibernateEntity
.name()));
}
/**
* Return an immutable collection of SQL statements that were executed by
* Hibernate for this transaction.
*
* @return a collection of SQL statements.
*/
@SuppressWarnings("unchecked")
public Collection<String> getSQLStatements() {
return Collections.unmodifiableCollection((Collection<? extends String>) get(HibernateTelemetryFields.SQLStatement
.name()));
}
/**
* Return an immutable set of unique table names involved in this Hibernate
* transaction.
*
* @return a distinct set of table names.
*/
@SuppressWarnings("unchecked")
public Collection<String> getTables() {
return Collections
.unmodifiableCollection((Collection<? extends String>) get(HibernateTelemetryFields.Table.name()));
}
/**
* Set the entities involved in the Hibernate transaction.
*
* @param entities
* The entities involved in the Hibernate transaction.
*/
public void setHibernateEntities(Collection<HibernateEntity> entities) {
put(HibernateTelemetryFields.HibernateEntity.name(), entities);
}
/**
* Set the collection of SQL statements involved in the Hibernate transaction.
*
* @param statements
* The SQL statements involved in the Hibernate transaction.
*/
public void setSQLStatements(Collection<String> statements) {
put(HibernateTelemetryFields.SQLStatement.name(), statements);
}
/**
* Set the collection of tables involved in the Hibernate transaction.
*
* @param tables
* The collection of tables involved in the transaction.
*/
public void setTables(Collection<String> tables) {
Set<String> tset = new HashSet<String>(tables.size());
tset.addAll(tables);
put(HibernateTelemetryFields.Table.name(), tset);
}
private String[] getTableFromSQL(String sql) {
String result[] = null;
StringTokenizer toks = new StringTokenizer(sql);
String op = toks.nextToken();
if (op.equalsIgnoreCase("select")) {
result = parseSelect(toks);
}
else if (op.equalsIgnoreCase("update")) {
result = parseUpdate(toks);
}
else if (op.equalsIgnoreCase("insert")) {
result = parseInsertAndDelete(toks);
}
else if (op.equalsIgnoreCase("delete")) {
result = parseInsertAndDelete(toks);
}
return result;
}
private String[] parseInsertAndDelete(StringTokenizer toks) {
// Next token is "from" or "into"
toks.nextToken();
return new String[] { toks.nextToken() };
}
private String[] parseSelect(StringTokenizer toks) {
// Tables live between "FROM" and either "WHERE" or end of statement.
//
while (toks.hasMoreTokens()) {
if (toks.nextToken().equalsIgnoreCase("from")) {
break;
}
}
// Reassemble the string until you reach the "where" or end. We have to
// split on commas
// now.
//
StringBuffer sb = new StringBuffer();
while (toks.hasMoreTokens()) {
String t = toks.nextToken();
if (t.equalsIgnoreCase("where")) {
break;
}
sb.append(" ");
sb.append(t);
}
StringTokenizer commas = new StringTokenizer(sb.toString(), ",");
List<String> tables = new ArrayList<String>();
while (commas.hasMoreTokens()) {
String tab = commas.nextToken();
int space = tab.indexOf(" ");
if (space > 0) {
tables.add(tab.substring(0, space));
}
else {
tables.add(tab);
}
}
if (tables.size() > 0) {
String[] result = new String[tables.size()];
tables.toArray(result);
return result;
}
else {
return null;
}
}
private String[] parseUpdate(StringTokenizer toks) {
return new String[] { toks.nextToken() };
}
}