// Last edited on 2014-12-17 02:35:41 by stolfilocal package quack; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; import java.util.TimeZone; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.text.ParseException; import javax.servlet.ServletContext; public class DatabaseImpl implements Database { private String dbName = null; // Nome do banco no servidor, ou {null}. private String dbLoginName; // Login a usar para conectar com o banco. private String dbPassword; // Senha a usar para conectar com o banco. private static final String DB_SERVER = "jdbc:mysql://sql2.lab.ic.unicamp.br:3306"; // Protocolo e hostname do servidor MySQL do IC. private Connection con = null; // Conexão com o banco, ou {null}. // ---------------------------------------------------------------------- // FORMATAÇÃO DE DATAHORAS PARA O BANCO DE DADOS // // As datahoras estão representadas no banco de dados em forma legível. // O formato deve ser imutável, e não é necessariamente o mesmo usado nas páginas HTML e outros // contextos visíveis pelo usuário. Por isso usamos funções próprias para conversão, // em vez das funções em {HTMLTools.java}. private static final String DB_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss"; // Formato de datahoras no banco de dados, menos o fuso horário. private static final String DB_TIMEZONE_CODE = "UTC"; // Fuso horário usado no banco de dados. private DateFormat dbTimestampConverter = null; // Conversor de datahoras entre formato usado no banco de dados (menos o fuso horário) // e o formato interno (número de segundos). @Override public Database initialize(String dbLoginName, String dbName, String dbPassword) { this.dbLoginName = dbLoginName; this.dbName = dbName; this.dbPassword = dbPassword; // Inicializa o conversor de datahoras para o formato e fuso horário usados no banco de dados: this.dbTimestampConverter = new SimpleDateFormat(DB_TIMESTAMP_FORMAT); this.dbTimestampConverter.setTimeZone(TimeZone.getTimeZone(DB_TIMEZONE_CODE)); return this; } private Connection getConnection() // Abre conexao {this} com servidor do banco de dados persistente. Retorna {null} se // {dbName} é {null}. { if (dbName == null) { return null; } try { if (con == null || con.isClosed()) { Class.forName("com.mysql.jdbc.Driver"); String dbURL = DB_SERVER + "/" + dbName; con = DriverManager.getConnection(dbURL, dbLoginName, dbPassword); con.setAutoCommit(false); } } catch (Exception e) { System.out.println("Erro ao criar conexão com o banco de dados."); e.printStackTrace(); System.exit(1); } return con; } @Override public void closeConnection() { try { if (con != null && !con.isClosed()) { con.close(); } } catch (SQLException e) { System.out.println("Erro ao encerrar conexão no banco de dados."); e.printStackTrace(); // Retorna mesmo assim. } } @Override public void loadDatabase(UserTable userTable, Long nextUserId, Long nextMessageId, ServletContext context) { try { getConnection(); if (con != null) { // Carrega todos os usuários e suas mensagens: String getAllUsersCmd = "SELECT * FROM user;"; ResultSet rs = makeSqlStatement(getAllUsersCmd).executeQuery(); while (rs.next()) { long userDbIndex = rs.getLong("userId"); String loginName = rs.getString("loginName"); String email = rs.getString("email"); String fullName = rs.getString("fullName"); String password = rs.getString("password"); long creationTime = parseDbTimestamp(rs.getString("creationTime")); User u = new UserImpl().initialize(loginName, email, fullName, password, creationTime, userDbIndex); if (u != null) { nextUserId = Math.max(nextUserId, u.getDbIndex()); // Carrega as mensagens do usuário: String getUserMessagesCmd = "SELECT * FROM message WHERE posterId=" + String.valueOf(u.getDbIndex()); ResultSet rs2 = makeSqlStatement(getUserMessagesCmd).executeQuery(); while (rs2.next()) { String body = rs2.getString("body"); long messageDbIndex = rs2 .getLong("messageId"); long postedTime = parseDbTimestamp(rs2.getString("postedTime")); Message m = new MessageImpl().initialize(u, body, messageDbIndex, postedTime); if (m == null) { System.out.println("Erro ao carregar mensagens"); } else { u.addMessage(m); nextMessageId = Math.max(nextMessageId, m.getDbIndex()); } } // Acrescenta o usuário à tabela: userTable.add(u); nextUserId = Math.max(nextUserId, u.getDbIndex()); } else { System.out.println("Problema no carregamento do usuário!"); } } // Carrega todos os contatos: String getAllContactsCmd = "SELECT * FROM contact;"; ResultSet rs3 = makeSqlStatement(getAllContactsCmd).executeQuery(); while (rs3.next()) { User s = userTable.getUserByDbIndex(rs3.getLong("source_id")); User t = userTable.getUserByDbIndex(rs3.getLong("target_id")); String st = rs3.getString("status"); Long lm = rs3.getLong("last_modified"); Contact c = new ContactImpl(); c.initialize(s, t, st, lm); s.addDirectContact(c); t.addReverseContact(c); } } } catch (SQLException e) { System.out.println("Erro ao carregar a base de dados."); e.printStackTrace(); System.exit(1); } } @Override public void addUser(User user) { try { getConnection(); if (con != null) { String insertUserCmd = "INSERT INTO user (userId, loginName, fullName, email, password, creationTime)"; String insertUserArgs = "VALUES (" + user.getDbIndex() + "," + "'" + user.getLoginName() + "'" + "," + "'" + user.getFullName() + "'" + "," + "'" + user.getEmail() + "'" + "," + "'" + user.getPassword() + "'" + "," + "'" + formatDbTimestamp(user.getCreationTime()) + "'" + ");"; makeSqlStatement(insertUserCmd + " " + insertUserArgs).execute(); commit(); System.out.println("Usuário " + user.getLoginName() + " inserido no banco de dados"); } } catch (SQLException e) { System.out.println("Erro ao acrescentar um usuário."); e.printStackTrace(); System.exit(1); } } @Override public void modifyUser(User user) { try { getConnection(); if (con != null) { String updateUserCmd = "UPDATE user"; String updateUserArgs = "SET " + "fullName='" + user.getFullName() + "'" + ", " + "password='" + user.getPassword() + "'" + ", " + "email='" + user.getEmail() + "'"; String updateUserWhere = "where userId='" + user.getDbIndex() + "'" + ";"; makeSqlStatement(updateUserCmd + " " + updateUserArgs + " " + updateUserWhere).execute(); commit(); System.out.println("Usuário " + user.getLoginName() + " alterado no banco de dados"); } } catch (SQLException e) { System.out.println("Erro ao modificar um usuário."); e.printStackTrace(); System.exit(1); } } @Override public void addContact (Contact contact) { try { getConnection(); if (con != null) { User source = contact.getSource(); User target = contact.getTarget(); String insertContactCmd = "INSERT INTO contact (sourceId, targetId, status, lastModTime)"; String insertContactArgs = "VALUES (" + source.getDbIndex() + "," + target.getDbIndex() + "," + "'" + contact.getStatus() + "'" + "," + "'" + formatDbTimestamp(contact.getLastModTime()) + "'" + ");"; makeSqlStatement(insertContactCmd + " " + insertContactArgs).execute(); commit(); System.out.println("Contato " + source.getLoginName() + " -> " + target.getLoginName() + " inserido no banco de dados"); } } catch (SQLException e) { System.out.println("Erro ao inserir um contato."); e.printStackTrace(); System.exit(1); } } @Override public void modifyContact(Contact contact) { try { getConnection(); if (con != null) { User source = contact.getSource(); User target = contact.getTarget(); String updateContactCmd = "UPDATE contact"; String updateContactArgs = "SET " + "status='" + contact.getStatus() + "'" + ", " + "lastModTime='" + formatDbTimestamp(contact.getLastModTime()) + "'"; String updateContactWhere = "where " + "sourceId='" + source.getDbIndex() + "'" + " and " + "targetId='" + target.getDbIndex() + "'" + ";"; makeSqlStatement(updateContactCmd + " " + updateContactArgs + " " + updateContactWhere).execute(); commit(); System.out.println("Contato " + source.getLoginName() + " -> " + target.getLoginName() + " alterado no banco de dados"); } } catch (SQLException e) { System.out.println("Erro ao alterar um contato."); e.printStackTrace(); System.exit(1); } } @Override public void addMessage(Message message) { try { getConnection(); if (con != null) { User poster = message.getPoster(); String insertMessageCmd = "INSERT INTO message (messageId, posterId, body, parentId, postedTime)"; String insertMessageArgs = "VALUES (" + message.getDbIndex() + "," + poster.getDbIndex() + "," + "'" + message.getBody() + "'" + "," + message.getParent().getDbIndex() + "," + "'" + formatDbTimestamp(message.getPostedTime()) + "'" + ");"; makeSqlStatement(insertMessageCmd + " " + insertMessageArgs).execute(); commit(); System.out.println("Mensagem " + message.getDbIndex() + " de " + poster.getLoginName() + " inserida no banco de dados"); } } catch (SQLException e) { System.out.println("Erro ao inserir uma mensagem."); e.printStackTrace(); System.exit(1); } } // Faz todas as alterações desde o último commit/rollback permanente e libera qualquer lock // do bando de dados para a esta conexão. Aborta o processo se a operação falhou. private void commit() { if (con != null) { try { con.commit(); } catch (SQLException e) { System.out.println("Erro no commit."); e.printStackTrace(); System.exit(1); } } } // Prepara uma query SQL pré compilada. Essa função não executa a query // para dar liberdade ao usuário de definir argumentos na string SQL // para, enfim, executá-la. Retorna {null} se houve algum problema. private PreparedStatement makeSqlStatement(String query){ PreparedStatement statement = null; try { statement = con.prepareStatement(query); } catch (SQLException e) { System.out.println("Erro ao preparar query do banco de dados."); e.printStackTrace(); System.exit(1); } return statement; } private long parseDbTimestamp(String dbTimestamp) // Converte uma datahora formatada {dbTimestamp}, recuperada do banco de dados, // para o formato interno. Supõe que o formato no banco de dados é // {DB_TIMESTAMP_FORMAT} no fuso {DB_TIMEONE_CODE}, seguido de {" " + DB_TIMEZONE}. { // Exige que o fuso horário seja UTC por enquanto. String timezone = DB_TIMEZONE_CODE; int tzk = dbTimestamp.indexOf(timezone); assert tzk >= 0; dbTimestamp = dbTimestamp.substring(0,tzk).trim(); try { Date date = this.dbTimestampConverter.parse(dbTimestamp); return date.getTime() / 1000; } catch (ParseException e) { System.out.println("Datahora inválida \"" + dbTimestamp + "\" no banco de dados"); e.printStackTrace(); System.exit(1); return 0L; // To satisfy the compiler. } } private String formatDbTimestamp(long timestamp) // Converte uma datahora {timestamp} do formato interno para o formato usado no // banco de dados. { return this.dbTimestampConverter.format(new Date(timestamp * 1000)) + " " + DB_TIMEZONE_CODE; } }