Quadcap Bug Database

com/quadcap/app/bugdb/BugSession.java

Go to the documentation of this file.
00001 package com.quadcap.app.bugdb; 00002 00003 /* Copyright 1999 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.ByteArrayInputStream; 00042 import java.io.ByteArrayOutputStream; 00043 import java.io.InputStream; 00044 import java.io.PrintWriter; 00045 00046 import java.util.Enumeration; 00047 import java.util.Hashtable; 00048 import java.util.Properties; 00049 import java.util.Vector; 00050 00051 00052 import java.sql.DriverManager; 00053 import java.sql.SQLException; 00054 00055 import java.net.URLDecoder; 00056 00057 import java.sql.Connection; 00058 import java.sql.PreparedStatement; 00059 import java.sql.ResultSet; 00060 import java.sql.ResultSetMetaData; 00061 import java.sql.Statement; 00062 import java.sql.SQLException; 00063 00064 import javax.servlet.ServletException; 00065 import javax.servlet.http.HttpServletRequest; 00066 import javax.servlet.http.HttpSession; 00067 import javax.servlet.http.HttpSessionBindingEvent; 00068 import javax.servlet.http.HttpSessionBindingListener; 00069 import javax.servlet.http.HttpSessionContext; 00070 00071 /** 00072 * A session bean encapsulating access to the Quadcap Bug Database. 00073 * JSP pages use methods of this bean to perform the various functions 00074 * of the bug database application. 00075 * 00076 * @author Stan Bailes 00077 */ 00078 public class BugSession implements HttpSessionBindingListener { 00079 Connection conn; 00080 00081 /** My user name. 'nobody' until logged in. */ 00082 String me = "nobody"; 00083 00084 String my_person_type = null; 00085 boolean isAdmin = false; 00086 boolean isManager = false; 00087 boolean isDeveloper = false; 00088 boolean isUser = false; 00089 00090 static Object lock = new Object(); 00091 00092 Vector debug_history = new Vector(); 00093 00094 PreparedStatement getComponentOwner; 00095 PreparedStatement insertBugHist; 00096 PreparedStatement insertPerson; 00097 PreparedStatement getBugHist; 00098 PreparedStatement getBug; 00099 PreparedStatement getQuery; 00100 PreparedStatement saveQuery; 00101 PreparedStatement getUser; 00102 PreparedStatement updateUser; 00103 PreparedStatement deleteUser; 00104 PreparedStatement getProject; 00105 PreparedStatement newProject; 00106 PreparedStatement deleteProject; 00107 PreparedStatement updateProject; 00108 00109 final String driverName = "com.quadcap.jdbc.JdbcDriver"; 00110 final String dbUrl = "jdbc:qed:bugdb;create=true"; 00111 final String dbUser = "bugdb"; 00112 final String dbPass = "bugdb"; 00113 00114 /** 00115 * Create a new session. 00116 */ 00117 public BugSession() { 00118 // Make sure we're connected to the database. If we're absolutely 00119 // the first user, load the schema. 00120 try { 00121 Class.forName(driverName); 00122 getConnection(); 00123 prepareStatements(); 00124 checkSchema(); 00125 } catch (Exception e) { 00126 e.printStackTrace(System.out); 00127 throw new RuntimeException(e.toString()); 00128 } 00129 } 00130 00131 /** 00132 * Return the JDBC connection for this session. 00133 * 00134 * @return this session's connection. 00135 */ 00136 public Connection getConnection() throws SQLException { 00137 if (conn == null) { 00138 Properties props = new Properties(); 00139 props.put("user", dbUser); 00140 props.put("password", dbPass); 00141 props.put("create", "true"); 00142 this.conn = DriverManager.getConnection(dbUrl, props); 00143 //conn.setAutoCommit(false); 00144 } 00145 return conn; 00146 } 00147 00148 /** 00149 * This application object has just been bound into an HttpSession 00150 * 00151 * @param event identifies the session and the name in the 00152 * session of this object. 00153 */ 00154 public void valueBound (HttpSessionBindingEvent event) { 00155 HttpSession session = event.getSession(); 00156 session.setMaxInactiveInterval(6 * 3600); // six hours 00157 } 00158 00159 /** 00160 * This application object has just been removed from an HttpSession 00161 * 00162 * @param event identifies the session and the name in the 00163 * session of this object. 00164 */ 00165 public void valueUnbound (HttpSessionBindingEvent event) { 00166 logout(null); 00167 try { 00168 conn.close(); 00169 } catch (Exception e) {} 00170 } 00171 00172 00173 /** 00174 * Prepare the various SQL statements we'll be using. 00175 * 00176 * @exception SQLException may be thrown 00177 */ 00178 private final void prepareStatements() throws SQLException { 00179 getComponentOwner = conn.prepareStatement( 00180 "select owner from component where component = ?"); 00181 00182 insertBugHist = conn.prepareStatement( 00183 "insert into bug_history (bug_id, hist_pos, submitter," + 00184 " abstract, description, comments, state, priority, type," + 00185 " owner, component, modtime) values(?,?,?,?,?,?,?,?,?,?,?,?)"); 00186 00187 insertPerson = conn.prepareStatement( 00188 "insert into person(name,password,email,person_type) " + 00189 "values(?,?,?,?,?)"); 00190 00191 getBugHist = conn.prepareStatement("select hist_size from bug where" + 00192 " bug_id = ? for update", 00193 ResultSet.TYPE_SCROLL_SENSITIVE, 00194 ResultSet.CONCUR_UPDATABLE); 00195 getBug = conn.prepareStatement("select * from bug_history " + 00196 "where bug_id = ? " + 00197 "order by hist_pos desc"); 00198 getQuery = conn.prepareStatement("select query from queries " + 00199 "where owner = ? and name = ?"); 00200 saveQuery = conn.prepareStatement("insert into queries values(?,?,?)"); 00201 getUser = conn.prepareStatement("select * from person where name=?"); 00202 updateUser = conn.prepareStatement( 00203 "select * from person where name=? for update", 00204 ResultSet.TYPE_SCROLL_SENSITIVE, 00205 ResultSet.CONCUR_UPDATABLE); 00206 deleteUser = conn.prepareStatement("delete from person where name=?"); 00207 getProject = conn.prepareStatement( 00208 "select * from component where component=?"); 00209 newProject = conn.prepareStatement( 00210 "insert into component values(?,?)"); 00211 updateProject = conn.prepareStatement( 00212 "select * from component where component=? for update", 00213 ResultSet.TYPE_SCROLL_SENSITIVE, 00214 ResultSet.CONCUR_UPDATABLE); 00215 deleteProject = conn.prepareStatement( 00216 "delete from component where component=?"); 00217 } 00218 00219 /** 00220 * Check to make sure the database schema exists. If not, load it 00221 * from the <code>bugdb.sql</code> file. 00222 * 00223 * @exception SQLException may be thrown. 00224 */ 00225 private final void checkSchema() throws SQLException { 00226 synchronized (lock) { 00227 Statement s = conn.createStatement(); 00228 ResultSet rs = null; 00229 try { 00230 rs = s.executeQuery("select * from singleton"); 00231 } catch (SQLException e) { 00232 Loader loader = new Loader(); 00233 loader.setConnection(conn); 00234 ClassLoader c = this.getClass().getClassLoader(); 00235 InputStream is = c.getResourceAsStream( 00236 "com/quadcap/app/bugdb/bugdb.sql"); 00237 loader.loadStream(is); 00238 } finally { 00239 if (rs != null) rs.close(); 00240 s.close(); 00241 } 00242 } 00243 } 00244 00245 /** 00246 * Is this session associated with a valid user? 00247 * 00248 * @return true if the user is valid. 00249 */ 00250 public boolean isUser() { return isUser; } 00251 00252 /** 00253 * Is this session associated with a user with 'developer' privileges? 00254 * 00255 * @return true if the user is valid and has developer privileges. 00256 */ 00257 public boolean isDeveloper() { return isDeveloper; } 00258 00259 /** 00260 * Is this session associated with a user with 'manager' privileges? 00261 * 00262 * @return true if the user is valid and has manager privileges. 00263 */ 00264 public boolean isManager() { return isManager; } 00265 00266 /** 00267 * Is this session associated with a user with 'administrator' privileges? 00268 * 00269 * @return true if the user is valid and has administrator privileges. 00270 */ 00271 public boolean isAdmin() { return isAdmin; } 00272 00273 00274 /** 00275 * Return the saved bug search query with the specified name 00276 * 00277 * @param name the name of the query 00278 * @return the query string 00279 * @exception ServletException may be thrown 00280 */ 00281 public String getQuery(String name) throws ServletException { 00282 try { 00283 getQuery.clearParameters(); 00284 getQuery.setString(1, me); 00285 getQuery.setString(2, name); 00286 ResultSet rs = getQuery.executeQuery(); 00287 try { 00288 if (rs.next()) { 00289 return rs.getString(1); 00290 } else { 00291 return ""; 00292 } 00293 } finally { 00294 rs.close(); 00295 } 00296 } catch (SQLException ex) { 00297 throw new ServletException(ex.toString()); 00298 } 00299 } 00300 00301 /** 00302 * Save a bug search query. 00303 * 00304 * @param name the query name 00305 * @param query the query string 00306 * @exception ServletException may be thrown 00307 */ 00308 public void saveQuery(String name, String query) throws ServletException { 00309 try { 00310 saveQuery.clearParameters(); 00311 saveQuery.setString(1, name); 00312 saveQuery.setString(2, query); 00313 saveQuery.setString(3, me); 00314 saveQuery.executeUpdate(); 00315 } catch (SQLException ex) { 00316 throw new ServletException(ex.toString()); 00317 } 00318 } 00319 00320 /** 00321 * Execute the 'logout' action. 00322 * 00323 * @param props the request properties for this action. Ignored. 00324 * 00325 */ 00326 public boolean logout(Properties props) { 00327 me = "nobody"; 00328 isAdmin = isManager = isDeveloper = isUser = false; 00329 if (conn != null) { 00330 try { 00331 conn.close(); 00332 } catch (Throwable t) {} 00333 conn = null; 00334 } 00335 return true; 00336 } 00337 00338 /** 00339 * Execute the 'login' action. 00340 * 00341 * @param props the request properties for this action. 00342 * <dl> 00343 * <dt>username</dt><dd>The user's login name</dd> 00344 * <dt>password</dt><dd>The user's password</dd> 00345 * </dl> 00346 * 00347 * @exception SQLException may be thrown 00348 */ 00349 public boolean login(Properties props) throws SQLException { 00350 String user = getString(props, "username"); 00351 String pass = getString(props, "password"); 00352 getConnection(); 00353 Statement s = conn.createStatement(); 00354 try { 00355 ResultSet rs = s.executeQuery( 00356 "select * from person where name = '" + user + 00357 "' and password = '" + pass + "'"); 00358 try { 00359 if (rs.next()) { 00360 me = user; 00361 my_person_type = rs.getString("person_type"); 00362 isAdmin = my_person_type.equals("Administrator"); 00363 isManager = isAdmin || my_person_type.equals("Manager"); 00364 isDeveloper = isManager || my_person_type.equals("Developer"); 00365 isUser = isDeveloper || my_person_type.equals("User"); 00366 return true; 00367 } 00368 } finally { 00369 rs.close(); 00370 } 00371 } finally { 00372 s.close(); 00373 } 00374 return false; 00375 } 00376 00377 /** 00378 * Return the next integer in the specified sequence. 00379 * 00380 * @return the next integer in this sequence. 00381 * @exception SQLException may be thrown 00382 */ 00383 int getNextId(String type) throws SQLException { 00384 Statement s = conn.createStatement( 00385 ResultSet.TYPE_SCROLL_SENSITIVE, 00386 ResultSet.CONCUR_UPDATABLE); 00387 try { 00388 int ret = 0; 00389 ResultSet rs = s.executeQuery( 00390 "select ivalue from singleton where name = '" + type + "'"); 00391 try { 00392 if (rs.next()) { 00393 ret = rs.getInt(1); 00394 rs.updateInt(1, ret + 1); 00395 rs.updateRow(); 00396 } 00397 } finally { 00398 rs.close(); 00399 } 00400 return ret; 00401 } finally { 00402 s.close(); 00403 } 00404 } 00405 00406 /** 00407 * Return the number of records in the bug history for the specified 00408 * bug. 00409 * 00410 * @param bug_id the bug number 00411 * @return the number of history records for this bug. 00412 * @exception SQLException may be thrown 00413 */ 00414 int getBugHistSize(int bug_id) throws SQLException { 00415 getBugHist.clearParameters(); 00416 getBugHist.setInt(1, bug_id); 00417 ResultSet rs = getBugHist.executeQuery(); 00418 try { 00419 if (rs.next()) { 00420 int ret = rs.getInt(1) + 1; 00421 rs.updateInt(1, ret); 00422 rs.updateRow(); 00423 return ret; 00424 } else { 00425 throw new SQLException("no bug: " + bug_id); 00426 } 00427 } finally { 00428 rs.close(); 00429 } 00430 } 00431 00432 /** 00433 * Execute the 'addUser' action. 00434 * 00435 * @param props the request properties for this action:<p> 00436 * <dl> 00437 * <dt>name</dt><dd>The user's login name</dd> 00438 * <dt>password</dt><dd>The user's password</dd> 00439 * <dt>password2</dt><dd>The user's password confirmation</dd> 00440 * <dt>email</dt><dd>The user's email address</dd> 00441 * <dt>person_type</dt><dd>The user's 'class', one of 00442 * <ul><li>User</li> 00443 * <li>Developer</li> 00444 * <li>Manager</li> 00445 * <li>Administrator</li> 00446 * </ul></dt> 00447 * </dl> 00448 * 00449 * @exception SQLException may be thrown 00450 */ 00451 public boolean addUser(Properties props) 00452 throws SQLException, ServletException 00453 { 00454 String p1 = getString(props, "password"); 00455 String p2 = getString(props, "password2"); 00456 if (!p1.equals(p2)) { 00457 throw new ServletException( 00458 "Password doesn't match confirm password"); 00459 } 00460 insertPerson.clearParameters(); 00461 insertPerson.setString(1, getString(props, "name")); 00462 insertPerson.setString(2, getString(props, "password")); 00463 insertPerson.setString(3, getString(props, "email")); 00464 00465 String person_type = "User"; 00466 if (isAdmin) { 00467 String t = getString(props, "person_type"); 00468 if (t != null && t.length() > 0) person_type = t; 00469 } 00470 insertPerson.setString(4, person_type); 00471 00472 return 1 == insertPerson.executeUpdate(); 00473 } 00474 00475 /** 00476 * Execute the 'updateUser' action. 00477 * @param props the request properties for this action:<p> 00478 * <dl> 00479 * <dt>name</dt><dd>The user's login name</dd> 00480 * <dt>password</dt><dd>The user's password</dd> 00481 * <dt>email</dt><dd>The user's email address</dd> 00482 * <dt>person_type</dt><dd>The user's 'class', one of 00483 * <ul><li>User</li> 00484 * <li>Developer</li> 00485 * <li>Manager</li> 00486 * <li>Administrator</li> 00487 * </ul></dt> 00488 * </dl> 00489 * 00490 * @exception SQLException may be thrown 00491 */ 00492 public boolean updateUser(Properties p) throws SQLException { 00493 updateUser.clearParameters(); 00494 updateUser.setString(1, p.getProperty("name")); 00495 ResultSet rs = updateUser.executeQuery(); 00496 try { 00497 if (rs.next()) { 00498 if (!p.getProperty("password").equals("password")) { 00499 rs.updateString("password", p.getProperty("password")); 00500 } 00501 rs.updateString("email", p.getProperty("email")); 00502 rs.updateString("person_type", p.getProperty("person_type")); 00503 rs.updateRow(); 00504 return true; 00505 } else { 00506 return false; 00507 } 00508 } finally { 00509 rs.close(); 00510 } 00511 } 00512 00513 /** 00514 * Execute the 'deleteUser' action 00515 * 00516 * @exception SQLException may be thrown 00517 */ 00518 public boolean deleteUser(Properties p) throws SQLException { 00519 deleteUser.clearParameters(); 00520 deleteUser.setString(1, p.getProperty("name")); 00521 return deleteUser.executeUpdate() == 1; 00522 } 00523 00524 /** 00525 * Generate an HTML <code>&lt;SELECT&gt;</code> element listing all 00526 * elements in the specified table. 00527 * 00528 * @param type the table (e.g., priority, state, etc.) 00529 * @return the HTML string. 00530 */ 00531 public String listOptions(String type) 00532 throws ServletException 00533 { 00534 return listOptions(type, null); 00535 } 00536 00537 /** 00538 * Generate an HTML <code>&lt;SELECT&gt;</code> element listing all 00539 * elements in the specified table. 00540 * 00541 * @param type the table (e.g., priority, state, etc.) Also the name 00542 * of the SELECT element, and the name of the column. 00543 * @param sel if non null, the value to be initially selected 00544 * @return the HTML string. 00545 */ 00546 public String listOptions(String type, String sel) 00547 throws ServletException 00548 { 00549 return listOptions(type, type, sel); 00550 } 00551 00552 /** 00553 * Generate an HTML <code>&lt;SELECT&gt;</code> element listing all 00554 * elements in the specified table. 00555 * 00556 * @param table the table (e.g., priority, state, etc.) Also the name 00557 * of the SELECT element. 00558 * @param type the name of the column containing the enumerated values. 00559 * @param sel if non null, the value to be initially selected 00560 * @return the HTML string. 00561 */ 00562 public String listOptions(String table, String type, String sel) 00563 throws ServletException 00564 { 00565 return listOptions(table, table, type, sel); 00566 } 00567 00568 /** 00569 * Generate an HTML <code>&lt;SELECT&gt;</code> element listing all 00570 * elements in the specified table. 00571 * 00572 * @param selType the name of the SELECT widget. 00573 * @param table the table (e.g., priority, state, etc.) 00574 * @param type the name of the column containing the enumerated values. 00575 * @param sel if non null, the value to be initially selected 00576 * @return the HTML string. 00577 */ 00578 public String listOptions(String seltype, String table, 00579 String type, String sel) 00580 throws ServletException 00581 { 00582 try { 00583 Statement s = conn.createStatement(); 00584 StringBuffer sb = new StringBuffer("\n<select name=\""); 00585 sb.append(seltype); 00586 sb.append("\">"); 00587 try { 00588 ResultSet rs = s.executeQuery("select " + type + 00589 " from " + table); 00590 try { 00591 while (rs.next()) { 00592 String t = rs.getString(1); 00593 sb.append("\n <option value=\""); 00594 sb.append(t); 00595 sb.append('"'); 00596 if (sel != null && t.equals(sel)) { 00597 sb.append(" selected"); 00598 } 00599 sb.append(">"); 00600 sb.append(t); 00601 sb.append("</option>"); 00602 } 00603 } finally { 00604 rs.close(); 00605 } 00606 sb.append("</select>"); 00607 return sb.toString(); 00608 } finally { 00609 s.close(); 00610 } 00611 } catch (SQLException e) { 00612 throw new ServletException(e.toString()); 00613 } 00614 } 00615 00616 /** 00617 * Generate an HTML <code>&lt;SELECT&gt;</code> element listing all 00618 * users. 00619 * 00620 * @param type the name of the SELECT element. 00621 * @param sel the value (if any) to be initially selected. 00622 */ 00623 public String listUsers(String type, String sel) throws ServletException { 00624 return listOptions(type, "person", "name", sel); 00625 } 00626 00627 /** 00628 * Return the name of the user who owns the specified component. 00629 * 00630 * @param component the component name 00631 * @return the component's owner 00632 * @exception SQLException may be thrown 00633 * @exception ServletException may be thrown 00634 */ 00635 public String getComponentOwner(String component) 00636 throws SQLException, ServletException 00637 { 00638 synchronized (getComponentOwner) { 00639 getComponentOwner.clearParameters(); 00640 getComponentOwner.setString(1, component); 00641 ResultSet rs = getComponentOwner.executeQuery(); 00642 try { 00643 if (rs.next()) { 00644 return rs.getString(1); 00645 } else { 00646 return "nobody"; 00647 } 00648 } finally { 00649 rs.close(); 00650 } 00651 } 00652 } 00653 00654 /** 00655 * Return an integer valued property 00656 * 00657 * @param props a properties object 00658 * @param name the name of the property to retrieve. 00659 * @return the property value 00660 * @exception ServletException is thrown if the property isn't a valid 00661 * integer. 00662 */ 00663 static int getInt(Properties props, String name) throws ServletException { 00664 try { 00665 return Integer.parseInt(props.getProperty(name)); 00666 } catch (Throwable e) { 00667 throw new ServletException("Missing or malformed parameter: " + 00668 name); 00669 } 00670 } 00671 00672 /** 00673 * Return a string-valued property. 00674 * 00675 * @param props a properties object 00676 * @param name the name of the property to retrieve. 00677 * @return the property value, or the empty string if the property 00678 * isn't defined. 00679 */ 00680 static String getString(Properties props, String name) { 00681 String s = props.getProperty(name); 00682 if (s == null) return ""; 00683 return s; 00684 } 00685 00686 /** 00687 * Execute the 'addBug' action. 00688 * 00689 * @param props the request properties, contains values for the various 00690 * fields in the 'bug' and 'bug_history' tables. 00691 * 00692 * @return true if this action succeeded. 00693 * @exception ServletException may be thrown. 00694 */ 00695 public boolean addBug(Properties props) throws ServletException { 00696 if (!isUser()) throw new ServletException("not logged in"); 00697 try { 00698 Statement s = conn.createStatement(); 00699 try { 00700 int bug_id = getNextId("bug"); 00701 int ret = s.executeUpdate( 00702 "insert into bug(bug_id, hist_size) values(" + 00703 bug_id + ",0)"); 00704 if (ret != 1) { 00705 throw new ServletException("add bug failed"); 00706 } 00707 00708 String component = getString(props, "component"); 00709 String owner = getComponentOwner(component); 00710 00711 insertBugHist.clearParameters(); 00712 insertBugHist.setInt(1, bug_id); // bug_id 00713 insertBugHist.setInt(2, 0); // hist_pos 00714 insertBugHist.setString(3, me); // submitter 00715 insertBugHist.setString(4, getString(props, "abstract")); 00716 insertBugHist.setString(5, getString(props, "description")); 00717 insertBugHist.setString(6, getString(props, "comments")); 00718 insertBugHist.setString(7, "Active"); // state 00719 insertBugHist.setString(8, getString(props, "priority")); 00720 insertBugHist.setString(9, getString(props, "type")); 00721 insertBugHist.setString(10, owner); // owner 00722 insertBugHist.setString(11, component); // component 00723 insertBugHist.setTimestamp(12, now()); // modtime 00724 ret = insertBugHist.executeUpdate(); 00725 if (ret != 1) { 00726 throw new ServletException("add bug history failed, ret = " + ret); 00727 } 00728 return true; 00729 } finally { 00730 s.close(); 00731 } 00732 } catch (SQLException e) { 00733 throw new ServletException(e.toString()); 00734 } 00735 } 00736 00737 /** 00738 * Execute the 'updateBug' action. 00739 * 00740 * @param props the request properties, contains values for the various 00741 * fields in the 'bug' and 'bug_history' tables. 00742 * 00743 * @return true if this action succeeded. 00744 * @exception ServletException may be thrown. 00745 */ 00746 public boolean updateBug(Properties props) throws ServletException { 00747 if (!isUser()) throw new ServletException("not logged in"); 00748 try { 00749 int bug_id = getInt(props, "bug_id"); 00750 int hist_pos = getBugHistSize(bug_id); 00751 00752 String component = getString(props, "component"); 00753 String owner = getComponentOwner(component); 00754 00755 insertBugHist.clearParameters(); 00756 insertBugHist.setInt(1, bug_id); // bug_id 00757 insertBugHist.setInt(2, hist_pos); // hist_pos 00758 insertBugHist.setString(3, me); // submitter 00759 insertBugHist.setString(4, getString(props, "abstract")); 00760 insertBugHist.setString(5, getString(props, "description")); 00761 insertBugHist.setString(6, getString(props, "comments")); 00762 insertBugHist.setString(7, getString(props, "state")); 00763 insertBugHist.setString(8, getString(props, "priority")); 00764 insertBugHist.setString(9, getString(props, "type")); 00765 insertBugHist.setString(10, getString(props, "owner")); 00766 insertBugHist.setString(11, getString(props, "component")); 00767 insertBugHist.setTimestamp(12, now()); // modtime 00768 if (insertBugHist.executeUpdate() != 1) { 00769 throw new ServletException("update bug history failed"); 00770 } 00771 return true; 00772 } catch (SQLException e) { 00773 throw new ServletException(e.toString()); 00774 } 00775 } 00776 00777 /** 00778 * Make a sql timestamp value representing the current time. 00779 * 00780 * @return a timestamp value. 00781 */ 00782 static final java.sql.Timestamp now() { 00783 return new java.sql.Timestamp(System.currentTimeMillis()); 00784 } 00785 00786 /** 00787 * Utility to do URL decoding without any exception handling. 00788 * Lazy bastard. 00789 * @param s the url to decode 00790 * @return the decoded url 00791 */ 00792 public final String urlDecode(String s) { 00793 try { 00794 if (s == null) return ""; 00795 return java.net.URLDecoder.decode(s); 00796 } catch (Throwable e) { 00797 return s; 00798 } 00799 } 00800 00801 /** 00802 * Perform the bug search operation. 00803 * 00804 * @param query the query string, a boolean predicate selecting bugs 00805 * to be returned. 00806 * @param ob an optional column name to use with an 'order by' clause, 00807 * to cause the list of bugs to be sorted based on that column. 00808 * @return a <code>Vector</code> of <code>Properties</code> objects, 00809 * where each Properties element contains entries for each column 00810 * in the search result. 00811 * @exception ServletException may be thrown 00812 */ 00813 public Vector searchBugs(String query, String ob) throws ServletException { 00814 Vector v = new Vector(); 00815 try { 00816 Statement s = conn.createStatement(); 00817 if (query == null) query = ""; 00818 query = query.trim(); 00819 if (query.length() > 0) { 00820 query = "and " + query; 00821 } 00822 if (ob != null) { 00823 query = query + " order by " + ob; 00824 } 00825 StringBuffer sb = new StringBuffer(); 00826 sb.append("select * from bug natural join bug_history where "); 00827 sb.append("(hist_size = hist_pos) " + query); 00828 ResultSet rs = s.executeQuery(sb.toString()); 00829 ResultSetMetaData rm = rs.getMetaData(); 00830 try { 00831 while (rs.next()) { 00832 Properties p = new Properties(); 00833 for (int i = 1; i <= rm.getColumnCount(); i++) { 00834 String name = rm.getColumnLabel(i); 00835 Object obj = rs.getObject(i); 00836 String val = (obj == null) ? "" : obj.toString(); 00837 p.put(name.toLowerCase(), val); 00838 } 00839 v.addElement(p); 00840 } 00841 } finally { 00842 rs.close(); 00843 } 00844 } catch (SQLException e) { 00845 throw new ServletException(e.toString()); 00846 } catch (Throwable t) { 00847 t.printStackTrace(System.out); 00848 } 00849 return v; 00850 } 00851 00852 /** 00853 * Turn the current row in the result set into a Hashtable. 00854 * 00855 * @param rs a result set 00856 * @return a Hashtable where the keys are the string-valued column labels 00857 * of the result set and the values are the string-valued column values 00858 * from the current row of the result set. 00859 * 00860 * @exception SQLException may be thrown 00861 */ 00862 final Hashtable getProps(ResultSet rs) throws SQLException { 00863 Hashtable p = new Hashtable(); 00864 ResultSetMetaData rm = rs.getMetaData(); 00865 for (int i = 1; i <= rm.getColumnCount(); i++) { 00866 String name = rm.getColumnLabel(i); 00867 String val = String.valueOf(rs.getObject(i)); 00868 p.put(name.toLowerCase(), val); 00869 } 00870 return p; 00871 } 00872 00873 /** 00874 * Return a list of valid users, as a Vector of Properties objects, 00875 * where each Hashtable object contains all of the user's properties 00876 * (except the password!) 00877 * 00878 * @return a list of valid users 00879 * @exception SQLException may be thrown 00880 */ 00881 public Vector getUsers() throws SQLException { 00882 Vector v = new Vector(); 00883 Statement s = conn.createStatement(); 00884 try { 00885 ResultSet rs = s.executeQuery("select * from person"); 00886 try { 00887 while (rs.next()) { 00888 Hashtable t = getProps(rs); 00889 t.remove("password"); 00890 v.addElement(t); 00891 } 00892 } finally { 00893 rs.close(); 00894 } 00895 } finally { 00896 s.close(); 00897 } 00898 return v; 00899 } 00900 00901 /** 00902 * Return a list of valid project/components, as a Vector of Hashtable 00903 * objects, 00904 * where each Hashtable object contains all of the user's properties 00905 * (except the password!) 00906 * 00907 * @return a list of valid components 00908 * @exception SQLException may be thrown 00909 */ 00910 public Vector getProjects() throws SQLException { 00911 Vector v = new Vector(); 00912 Statement s = conn.createStatement(); 00913 try { 00914 ResultSet rs = s.executeQuery("select * from component"); 00915 try { 00916 while (rs.next()) { 00917 Hashtable t = getProps(rs); 00918 v.addElement(t); 00919 } 00920 } finally { 00921 rs.close(); 00922 } 00923 } finally { 00924 s.close(); 00925 } 00926 return v; 00927 } 00928 00929 /** 00930 * Return the properties for a single user. 00931 * 00932 * @param user the user name 00933 * @return the user's properties (except the password) 00934 * @exception SQLException may be thrown 00935 */ 00936 public Hashtable getUser(String user) throws SQLException { 00937 getUser.clearParameters(); 00938 getUser.setString(1, user); 00939 ResultSet rs = getUser.executeQuery(); 00940 try { 00941 if (rs.next()) { 00942 Hashtable t = getProps(rs); 00943 t.remove("password"); 00944 return t; 00945 } else { 00946 return null; 00947 } 00948 } finally { 00949 rs.close(); 00950 } 00951 } 00952 00953 /** 00954 * Return the properties for a single component. 00955 * 00956 * @param name the component name 00957 * @return the component's properties 00958 * @exception SQLException may be thrown 00959 */ 00960 public Hashtable getProject(String user) throws SQLException { 00961 getProject.clearParameters(); 00962 getProject.setString(1, user); 00963 ResultSet rs = getProject.executeQuery(); 00964 try { 00965 if (rs.next()) { 00966 Hashtable t = getProps(rs); 00967 return t; 00968 } else { 00969 return null; 00970 } 00971 } finally { 00972 rs.close(); 00973 } 00974 } 00975 00976 /** 00977 * Execute the 'newProject' action. 00978 * 00979 * @param props the request properties for this action:<p> 00980 * <dl> 00981 * <dt>component</dt><dd>The name of the project</dd> 00982 * <dt>owner</dt><dd>The owner of the project.</dd> 00983 * </dl> 00984 * 00985 * @exception SQLException may be thrown 00986 */ 00987 public boolean newProject(Properties p) throws SQLException { 00988 newProject.clearParameters(); 00989 newProject.setString(1, p.getProperty("component")); 00990 newProject.setString(2, p.getProperty("owner")); 00991 return 1 == newProject.executeUpdate(); 00992 } 00993 00994 /** 00995 * Execute the 'updateProject' action. 00996 * @param props the request properties for this action:<p> 00997 * <dl> 00998 * <dt>component</dt><dd>The name of the project</dd> 00999 * <dt>owner</dt><dd>The owner of the project.</dd> 01000 * </dl> 01001 * 01002 * @exception SQLException may be thrown 01003 */ 01004 public boolean updateProject(Properties p) throws SQLException { 01005 updateProject.clearParameters(); 01006 updateProject.setString(1, p.getProperty("component")); 01007 ResultSet rs = updateProject.executeQuery(); 01008 try { 01009 if (rs.next()) { 01010 rs.updateString("owner", p.getProperty("owner")); 01011 rs.updateRow(); 01012 return true; 01013 } else { 01014 return false; 01015 } 01016 } finally { 01017 rs.close(); 01018 } 01019 } 01020 01021 /** 01022 * Execute the 'deleteProject' action 01023 * 01024 * @exception SQLException may be thrown 01025 */ 01026 public boolean deleteProject(Properties p) throws SQLException { 01027 deleteProject.clearParameters(); 01028 deleteProject.setString(1, p.getProperty("component")); 01029 return deleteProject.executeUpdate() == 1; 01030 } 01031 01032 /** 01033 * The properties that we ignore when computing deltas between 01034 * successive bug history records. 01035 */ 01036 static Hashtable nonDeltas = new Hashtable(); 01037 static { 01038 nonDeltas.put("modtime", ""); 01039 nonDeltas.put("comments", ""); 01040 } 01041 01042 /** 01043 * Given two bug history records, return a hashtable containing entries 01044 * for each field which changed between the two records. 01045 * 01046 * @param a the newer history record 01047 * @param b the older history record 01048 * @return a Hashtable where the keys are the names of the changed fields 01049 * and the values are strings of the form:<p> 01050 * <pre>Changed from <i>bval</i> to <i>aval</i></pre> 01051 */ 01052 Hashtable diffProps(Hashtable a, Hashtable b) { 01053 Hashtable t = new Hashtable(); 01054 Enumeration e = a.keys(); 01055 while (e.hasMoreElements()) { 01056 String key = (String)e.nextElement(); 01057 if (key.equals("history")) continue; 01058 if (key.equals("hist_pos")) continue; 01059 String aval = (String)a.get(key); 01060 String bval = (String)b.get(key); 01061 if (!aval.equals(bval)) { 01062 if (nonDeltas.get(key) != null) { 01063 t.put(key, bval); 01064 } else { 01065 t.put(key, "Changed from " + bval + " to " + aval); 01066 } 01067 } 01068 } 01069 return t; 01070 } 01071 01072 /** 01073 * Return the current information and history for the specified bug. 01074 * 01075 * @param bug_id 01076 * @return a Hashtable where the keys are the names of the bug 01077 * properties and the values are the current (i.e., most recent) 01078 * values for those properties. The special key <i>"history"</i> 01079 * has a Vector of Hashtables value containing the reverse-chronological 01080 * history deltas for the bug. 01081 * 01082 * @exception ServletException may be thrown 01083 */ 01084 public Hashtable getBug(String bug_id) throws ServletException { 01085 try { 01086 getBug.clearParameters(); 01087 getBug.setInt(1, Integer.parseInt(bug_id)); 01088 ResultSet rs = getBug.executeQuery(); 01089 try { 01090 Hashtable ret = null; 01091 Vector hist = new Vector(); 01092 Hashtable last = null; 01093 String description = ""; 01094 while (rs.next()) { 01095 Hashtable props = getProps(rs); 01096 description = rs.getString("Description"); 01097 if (ret == null) { 01098 ret = props; 01099 ret.put("history", hist); 01100 } else { 01101 hist.addElement(diffProps(last, props)); 01102 } 01103 last = props; 01104 } 01105 // take the 'last' description. Cause the results are returned 01106 // newest first, this gives us the original bug description. 01107 if (ret == null) ret = new Hashtable(); 01108 ret.put("description", description); 01109 return ret; 01110 } finally { 01111 rs.close(); 01112 } 01113 } catch (SQLException e) { 01114 throw new ServletException(e.toString()); 01115 } 01116 } 01117 01118 /** 01119 * Replace occurrences of &lt;, &gt;, and &amp; with their HTML 01120 * entity-representation equivalents. 01121 * 01122 * @param the the string to encode 01123 * @return the encoded string 01124 */ 01125 public String htmlEncode(String s) { 01126 try { 01127 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 01128 PrintWriter pw = new PrintWriter(bos); 01129 HtmlWriter w = new HtmlWriter(pw); 01130 for (int i = 0; i < s.length(); i++) { 01131 w.write(s.charAt(i)); 01132 } 01133 w.flush(); 01134 return bos.toString(); 01135 } catch (Exception e) { 01136 return s; 01137 } 01138 } 01139 01140 /** 01141 * The main request handler. Determine which action is to be invoked 01142 * and dispatch the appropriate routine. 01143 * 01144 * @param req the http request 01145 * @exception ServletException may be thrown 01146 * @exception SQLException may be thrown 01147 */ 01148 public void handleRequest(HttpServletRequest req) 01149 throws ServletException, SQLException 01150 { 01151 String action = req.getParameter("action"); 01152 boolean commit = false; 01153 try { 01154 if (action != null) { 01155 Properties p = getRequestProperties(req); 01156 if (action.equals("login")) { 01157 commit = login(p); 01158 } else if (action.equals("logout")) { 01159 commit = logout(p); 01160 } else if (!isUser()) { 01161 throw new ServletException("not logged in"); 01162 } else if (action.equals("newbug")) { 01163 commit = addBug(p); 01164 } else if (action.equals("modify")) { 01165 commit = updateBug(p); 01166 } else if (action.equals("register")) { 01167 commit =addUser(p); 01168 } else if (action.equals("updateUser")) { 01169 commit = updateUser(p); 01170 } else if (action.equals("deleteUser")) { 01171 commit = deleteUser(p); 01172 } else if (action.equals("newUser")) { 01173 commit = addUser(p); 01174 } else if (action.equals("newProject")) { 01175 commit = newProject(p); 01176 } else if (action.equals("updateProject")) { 01177 commit = updateProject(p); 01178 } else if (action.equals("deleteProject")) { 01179 commit = deleteProject(p); 01180 } else if (action.equals("noop")) { 01181 } else { 01182 throw new ServletException("Bad action: " + action); 01183 } 01184 } else if (!isUser()) { 01185 throw new ServletException("not logged in"); 01186 } 01187 } finally { 01188 // if (commit && conn != null) { 01189 // conn.commit(); 01190 // } else { 01191 // conn.rollback(); 01192 // } 01193 } 01194 } 01195 01196 /** 01197 * Build a Properties object containing all of the request parameters. 01198 * 01199 * @param req the http request 01200 * @return the request parameters 01201 */ 01202 public Properties getRequestProperties(HttpServletRequest req) { 01203 Properties p = new Properties(); 01204 Enumeration e = req.getParameterNames(); 01205 while (e.hasMoreElements()) { 01206 String name = (String)e.nextElement(); 01207 String val = req.getParameter(name); 01208 try { 01209 val = URLDecoder.decode(val); 01210 } catch (Throwable ex) {} 01211 p.put(name, val); 01212 } 01213 return p; 01214 } 01215 }