/******************************************************************************* * Copyright (c) 2009 the CHISEL group and contributors. 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: the CHISEL group - initial API and implementation *******************************************************************************/ package ca.uvic.chisel.javasketch.data.internal; import java.io.IOException; import java.io.StringReader; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.TreeSet; import org.eclipse.core.runtime.CoreException; import ca.uvic.chisel.hsqldb.server.HSQLTrigger; import ca.uvic.chisel.hsqldb.server.IDataPortal; /** * DataUtils used for writing. * * @author Del Myers * */ public class WriteDataUtils extends DataUtils { private static final String CREATE_THREAD_STATEMENT = "INSERT INTO Thread VALUES (?, ?, ?, ?)"; private static final String CREATE_EVENT_STATEMENT = "INSERT INTO Event VALUES (?, ?, ?)"; private static final String CREATE_ACTIVATION_STATEMENT = "INSERT INTO Activation VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; private static final String CREATE_DATA_STATEMENT = "INSERT INTO Data VALUES (?, ?, ?, ?, ?, ?)"; private static final String CREATE_MESSAGE_STATEMENT = "INSERT INTO Message VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; private static final String IDENTITY_CALL = "CALL IDENTITY()"; private long messageID; private ExecutableQuery createMessageStatement; private long dataID; private ExecutableQuery createDataStatement; private long activationID; private ExecutableQuery createActivationStatement; private long eventID; private ExecutableQuery createEventStatement; private long threadID; private ExecutableQuery createThreadStatement; private DataTrigger dataTrigger; private String triggerName; /** * @param c * @throws SQLException */ public WriteDataUtils(IDataPortal portal) throws SQLException { super(portal); prepareWriteStatements(); this.dataTrigger = new DataTrigger(); } /** * Drops and recreates all the tables required for storing the trace data. A * new entry for the trace is created. * @throws CoreException */ public synchronized void initializeDB(String launchId, Date timestamp) throws SQLException, IOException { messageID = dataID = activationID = eventID = threadID = 0; getPortal().setWritable(true); Statement s = getWriteConnection().createStatement(); dropTrigger(s, launchId); dropView("TraceClass"); dropView("Method"); dropTable("Event"); dropTable("Activation"); dropTable("Message"); dropTable("Data"); dropTable("Thread"); dropTable("Trace"); dropTable("Method"); dropTable("TraceClass"); dropTable("TraceClassTemp"); dropTable("MethodTemp"); this.order_num = 0L; Timestamp time = new Timestamp(timestamp.getTime()); // create the tables. s.execute("CREATE TABLE Trace (" + "model_id BIGINT PRIMARY KEY," + "launch_id VARCHAR(256)," + "time TIMESTAMP," + "max_id BIGINT, data_time TIMESTAMP)"); // insert a row for the trace String sql = "INSERT INTO Trace VALUES(0, '" + launchId + "', '" + time.toString() + "', 0, '" + new Timestamp(System.currentTimeMillis()) + "')"; s.execute(sql); // s.execute("CREATE TABLE TraceClass (" // + "model_id BIGINT," // + "name VARCHAR(512)," // + "CONSTRAINT UNIQUE_NAME UNIQUE (name)" // + ")"); // // s.execute("CREATE TABLE Method (" // + "model_id BIGINT PRIMARY KEY," // + "type_id BIGINT," // + "name VARCHAR(256)," // + "signature VARCHAR(512))"); s .execute("CREATE TABLE Event (" + "model_id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1)," + "time BIGINT," + "text VARCHAR(128)" + ")"); s .execute("CREATE TABLE Thread (" + "model_id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1)," + "thread_id INT," + "thread_name VARCHAR(128)," + "root_id BIGINT," + "CONSTRAINT UNIQUE_THREAD UNIQUE (thread_id)," + "CONSTRAINT UNIQUE_ROOT UNIQUE (root_id))"); s .execute("CREATE TABLE Message (" + "model_id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1), " + "kind VARCHAR(10), " + "activation_id BIGINT, " + "opposite_id BIGINT, " + "order_num BIGINT, " + "time BIGINT, " + "code_line INT, " + "sequence VARCHAR(8000))"); s.execute("CREATE INDEX IDX_ACTIVATION ON Message (activation_id)"); s.execute("CREATE INDEX IDX_KIND ON Message (kind)"); s.execute("CREATE INDEX IDX_TIME ON Message (time)"); s.execute("CREATE INDEX IDX_ORDER ON Message (order_num)"); s.execute("CREATE INDEX IDX_OPPOSITE ON Message (opposite_id)"); s .execute("CREATE TABLE Data (" + "model_id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1)," + "type_id BIGINT," + "ref_id BIGINT," + "instance VARCHAR(512), " + "value VARCHAR(512)," + "order_num INT)"); s.execute("CREATE INDEX IDX_TYPE ON Data (type_id)"); s.execute("CREATE INDEX IDX_REFERENCE ON Data (ref_id)"); s.execute("CREATE INDEX IDX_INSTANCE ON Data (instance)"); s .execute("CREATE TABLE Activation (" + IActivationTable.columnNames[IActivationTable.MODEL_ID] + " BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1)," + IActivationTable.columnNames[IActivationTable.ARRIVAL_ID] + " BIGINT, " + IActivationTable.columnNames[IActivationTable.TYPE_NAME] + " VARCHAR(128)," + IActivationTable.columnNames[IActivationTable.METHOD_NAME] + " VARCHAR(128)," + IActivationTable.columnNames[IActivationTable.METHOD_SIGNATURE] + " VARCHAR(512)," + IActivationTable.columnNames[IActivationTable.THREAD_ID] + " BIGINT," + IActivationTable.columnNames[IActivationTable.THIS_TYPE] + " VARCHAR(128)," + IActivationTable.columnNames[IActivationTable.INSTANCE] + " VARCHAR(512))"); s.execute("CREATE INDEX IDX_CLASS ON Activation (type_name)"); s.execute("CREATE INDEX IDX_THIS ON Activation (this_type)"); s .execute("CREATE INDEX IDX_METHOD ON Activation (type_name, method_name, method_signature)"); s.execute("CREATE INDEX IDX_THREAD ON Activation (thread_id)"); s.execute("CREATE INDEX IDX_ARRIVAL ON Activation (arrival_id)"); s .execute("CREATE VIEW TraceClass AS SELECT DISTINCT type_name FROM Activation AS type_name UNION (SELECT DISTINCT this_type FROM Activation AS type_name)"); s .execute("CREATE VIEW Method AS SELECT DISTINCT type_name, method_name, method_signature FROM Activation"); createTrigger(s, launchId); // System.out.println(launchId); } /** * @param s * @param launchId * @throws SQLException */ private void createTrigger(Statement s, String launchId) throws SQLException { triggerName = launchId .replaceAll( "(\\s|[\\.\\-~\\!@#\\$;%^\\*\\(\\)\\+=\\{\\}\\[\\]\\|\\<\\>])", "_"); triggerName = triggerName.toUpperCase(); HSQLTrigger.registerWeakTrigger(triggerName, "ACTIVATION", dataTrigger); s .execute("CREATE TRIGGER " + triggerName + " AFTER INSERT ON ACTIVATION FOR EACH ROW CALL \"ca.uvic.chisel.hsqldb.server.HSQLTrigger\""); } /** * @param launchId */ private void dropTrigger(Statement s, String launchId) { String triggerName = launchId.replace(" ", "_"); triggerName = triggerName.replace(".", "_"); triggerName = triggerName.toUpperCase(); try { s.execute("DROP TRIGGER " + triggerName); } catch (SQLException e) { } } /** * Commits data that is currently stored in views into tables so that they * can be queried more quickly later. The database isn't committed after * this call, clients should call {@link #commit()}. * * @throws SQLException */ public synchronized void storeViews() throws SQLException { Statement s = getWriteConnection().createStatement(); s.execute("SELECT * INTO TraceClassTemp FROM TraceClass"); s.execute("SELECT * INTO MethodTemp FROM Method"); // s.execute("SELECT * INTO TraceClassTemp FROM TraceClass"); // s.execute("SELECT * INTO MethodTemp FROM MethodView"); dropView("TraceClass"); dropView("Method"); s.execute("ALTER TABLE TraceClassTemp RENAME TO TraceClass"); s.execute("ALTER TABLE MethodTemp RENAME TO Method"); s.execute("CREATE INDEX IDX_METHOD_TYPE ON Method(type_name)"); s.execute("CREATE INDEX IDX_CLASS_TYPE ON TraceClass(type_name)"); // re-prepare the statements to match the tables. methodBySignatureStatement = new ExecutableQuery("SELECT type_name, method_name, method_signature from Method where type_name=? and method_name=? and method_signature=?"); methodsByTypeStatement = new ExecutableQuery("select type_name, method_name, method_signature from Method where type_name=?"); // ResultSet results2 = getWriteConnection().createStatement() // .executeQuery("Select type_name from TraceClass"); // TreeSet<String> typeNames = new TreeSet<String>(); // while (results2.next()) { // typeNames.add(results2.getString(1)); // } // results2 = getWriteConnection().createStatement().executeQuery( // "Select DISTINCT type_name from Method"); // while (results2.next()) { // typeNames.remove(results2.getString(1)); // } // ResultSet results = getWriteConnection().createStatement().executeQuery("Select * from Method"); // while (results.next()) { // String name = results.getString("method_name"); // System.out.println(name); // } } /** * @param string */ private void dropTable(String tableName) throws SQLException { try { getWriteConnection().createStatement().execute( "DROP TABLE " + tableName); } catch (SQLException e) { // just ignore it. } } private void dropView(String tableName) throws SQLException { try { getWriteConnection().createStatement().execute( "DROP VIEW " + tableName); } catch (SQLException e) { // just ignore it. } } /** * Creates a new message. The order number of the messages is automatically * updated. * * @param kind * must be one of {@link #MESSAGE_KIND_ARRIVE}, * {@link #MESSAGE_KIND_CALL}, {@link #MESSAGE_KIND_CATCH}, * {@link #MESSAGE_KIND_CATCH}, {@link #MESSAGE_KIND_REPLY}, * {@link #MESSAGE_KIND_RETURN}, or {@value #MESSAGE_KIND_THROW} * @param activation_id * @param opposite_id * the id of the opposite end of this message. May be null in the * case of an arrival. * @param time * the time from the start of the trace (in milliseconds). * @param codeLine * the line of code that this message refers to. * @param sequence * (for calls, replys, and thows only) the sequence that this * call occurs in in the thread. * @return the id of the newly created message. * @throws SQLException */ public synchronized long createMessage(String kind, long activation_id, Long opposite_id, long time, int codeLine, String sequence) throws SQLException { /* * "model_id", "kind", "activation_id", "opposite_id", "order", "time", * "code_line", "sequence" */ String storedSequence = toStoredSequence(sequence); messageID++; createMessageStatement.setLong(1, messageID); createMessageStatement.setString(2, kind); createMessageStatement.setLong(3, activation_id); createMessageStatement.setObject(4, opposite_id); createMessageStatement.setLong(5, order_num++); createMessageStatement.setLong(6, time); createMessageStatement.setInt(7, codeLine); createMessageStatement.setString(8, storedSequence); createMessageStatement.execute(); // incrementModelNumStatement.execute(); return messageID; } /** * Takes a string of the form 'x.y.z'... and translates it to a form * that can be stored in the database. * @param sequence * @return */ public static String toStoredSequence(String sequence) { if (sequence == null) { return null; } long[] sequenceNumbers = splitSequence(sequence); if (sequenceNumbers == null) { return null; } return toStoredSequence(sequenceNumbers); } /** * Translates the array of numbers to a string that can be stored in the * database. * @param sequenceNumbers * @return */ public static String toStoredSequence(long[] sequenceNumbers) { StringBuilder builder = new StringBuilder(); long _32bit = (long)Math.pow(2, 32); for (long x : sequenceNumbers) { long y = x+2; if (y > _32bit) { builder.append('\1'); char a = (char)((y & 0xFFFF00000000L) >> 32); char b = (char)((y & 0xFFFF0000L) >> 16); char c = (char)(y & 0xFFFFL); builder.append(a); builder.append(b); builder.append(c); } else if (y > Character.MAX_VALUE) { builder.append('\0'); char a = (char)((y & 0xFFFF0000L) >> 16); char b = (char)((y & 0xFFFF)); builder.append(a); builder.append(b); } else { builder.append((char)y); } } return builder.toString(); } /** * Takes the stored sequence in storedSequence (from a database) and * converts it to a list of numbers. * @param storedSequence * @return */ public static List<Long> fromStoredSequence(String storedSequence) { StringReader reader = new StringReader(storedSequence); LinkedList<Long> sequence = new LinkedList<Long>(); int c = -1; try { while ((c = reader.read()) > -1) { if (c == 0) { //read the next two characters int a = reader.read(); int b = reader.read(); if (b < 0) { return null; } long x = (long)(((a << 16) + b)-2); sequence.add(x); } else if (c == 1) { //read the next two characters int x = reader.read(); int y = reader.read(); int z = reader.read(); if (c < 0) { return null; } long w = (long)(((x << 32) + (y << 16) + z)-2); sequence.add(w); } else { sequence.add((long)(c-2)); } } } catch (IOException e) { return null; } return sequence; } /** * Joins the list of numbers as a dot-separated string. * @param sequence * @return */ public static String joinSequence(Collection<Long> sequence) { if (sequence == null) { return null; } StringBuilder builder = new StringBuilder(); int i = 0; for (long l : sequence) { builder.append(l); i++; if (i < sequence.size()) { builder.append('.'); } } return builder.toString(); } /** * Returns the stored sequence as a dot-separated string. * @param storedSequence * @return */ public static String fromStoredSequenceString(String storedSequence) { return joinSequence(fromStoredSequence(storedSequence)); } /** * @param sequence * @return */ private static long[] splitSequence(String sequence) { if (sequence.isEmpty()) { return new long[0]; } String[] split = sequence.split("\\."); long[] numbers = new long[split.length]; for (int i = 0; i < split.length; i++) { try { numbers[i] = Long.parseLong(split[i]); } catch (NumberFormatException e) { return null; } } return numbers; } public synchronized long createThread(String thread_id, String thread_name, long first_arrival_id) throws SQLException { threadID++; createThreadStatement.setLong(1, threadID); createThreadStatement.setString(2, thread_id); createThreadStatement.setString(3, thread_name); createThreadStatement.setLong(4, first_arrival_id); createThreadStatement.execute(); // incrementModelNumStatement.execute(); return threadID; } public synchronized long createActivation(long arrival_id, String type_name, String method_name, String method_signature, long thread_id, String this_type, String instance) throws SQLException { activationID++; createActivationStatement.setLong(1, activationID); createActivationStatement.setLong(2, arrival_id); createActivationStatement.setString(3, type_name); createActivationStatement.setString(4, method_name); createActivationStatement.setString(5, method_signature); createActivationStatement.setLong(6, thread_id); createActivationStatement.setString(7, this_type); createActivationStatement.setString(8, instance); createActivationStatement.execute(); // incrementModelNumStatement.execute(); return activationID; } /** * @param typeId * @param modelID * @param instance * @param string * @param i * @throws SQLException */ public synchronized long createData(long typeId, long refId, String instance, String value, int order) throws SQLException { dataID++; createDataStatement.setLong(1, dataID); createDataStatement.setLong(2, typeId); createDataStatement.setLong(3, refId); createDataStatement.setString(4, instance); createDataStatement.setString(5, value); createDataStatement.setInt(6, order); createDataStatement.execute(); return dataID; } /** * @return * @throws SQLException */ private long getLastIdentity() throws SQLException { // if (true) return 1; ResultSet results = getPortal().prepareCall(IDENTITY_CALL).executeQuery(); if (results.next()) { return results.getLong(1); } return -1; } /** * Forces a commit in the database. * * @throws SQLException * if an error occurred in the commit */ public void commit() throws SQLException { createActivationStatement.commit(); createDataStatement.commit(); createEventStatement.commit(); createMessageStatement.commit(); createThreadStatement.commit(); getWriteConnection().commit(); } public void compact() throws SQLException { commit(); // set a checkPoint getWriteConnection().createStatement().execute("CHECKPOINT DEFRAG"); } public synchronized long nextMessageID() throws SQLException { return messageID + 1; } public synchronized long nextActivationID() throws SQLException { return activationID + 1; } public synchronized long createEvent(long time, String text) throws SQLException { eventID++; createEventStatement.setLong(1, eventID); createEventStatement.setLong(2, time); createEventStatement.setString(3, text); createEventStatement.execute(); return eventID; } public void addTriggerListener(IDataTriggerListener listener) { dataTrigger.addDataListener(listener); } /** * @param traceImpl */ public void removeTriggerListener(IDataTriggerListener listener) { dataTrigger.removeDataListener(listener); } private synchronized Connection getWriteConnection() throws SQLException { return getPortal().getDefaultConnection(false); } /** * @throws SQLException */ private void prepareWriteStatements() throws SQLException { createMessageStatement = new ExecutableQuery(CREATE_MESSAGE_STATEMENT); createDataStatement = new ExecutableQuery(CREATE_DATA_STATEMENT); createActivationStatement = new ExecutableQuery(CREATE_ACTIVATION_STATEMENT); createEventStatement = new ExecutableQuery(CREATE_EVENT_STATEMENT); createThreadStatement = new ExecutableQuery(CREATE_THREAD_STATEMENT); } /* (non-Javadoc) * @see ca.uvic.chisel.javasketch.data.internal.DataUtils#reset() */ @Override public void reset() { super.reset(); } public void dispose() { } }