001package bradleyross.j2ee.servlets; 002import java.io.*; 003import java.sql.SQLException; 004import java.sql.Types; 005import java.sql.ResultSet; 006import java.sql.ResultSetMetaData; 007import javax.servlet.*; 008import javax.servlet.http.*; 009import bradleyross.library.database.DatabaseProperties; 010/** 011 * Serves as a template for writing servlets that 012 * display the contains of a java.sql.ResultSet object 013 * as a CSV (comma separated values) page. 014 * 015 * <p>The methods getResultSet and makeConnection are 016 * abstract and will need to be overridden before 017 * this class can be used. The user may also wish to 018 * override several of the methods for formatting 019 * information.</p> 020 * <p>This code needs to be changed since many of the fields that belong 021 * to the instance must be moved within the service method since there 022 * can be multiple copies of the service method running at the same time.</p> 023 * @see #getResultSet(HttpServletRequest, DatabaseProperties) 024 * @author Bradley Ross 025 */ 026public abstract class CsvWriter extends HttpServlet 027{ 028 /** 029 * Provided for compliance with Serializable interface. 030 */ 031 private static final long serialVersionUID = 1L; 032 /** 033 * ServletConfig object as passed to the init method. 034 * <p>Must be moved to inner class</p> 035 */ 036 protected ServletConfig config = null; 037 /** 038 * Called by the servlet container when the servlet object is first placed 039 * in service. 040 */ 041 public void init(ServletConfig configIn) throws ServletException 042 { 043 super.init(configIn); 044 config = configIn; 045 } 046 /** 047 * Called by the servlet container when the servlet object is being taken out 048 * of service. 049 */ 050 public void destroy() 051 { super.destroy(); } 052 /** 053 * Encapsulates methods and properties for a given instance of the 054 * service method. 055 * 056 * <p>Methods and properties in this class will normally not be 057 * overridden.</p> 058 * @author Bradley Ross 059 * 060 */ 061 protected class Instance extends Object 062 { 063 /** 064 * Object containing HTTP request. 065 */ 066 protected HttpServletRequest request = null; 067 /** 068 * Object used for preparing HTTP response. 069 */ 070 protected HttpServletResponse response = null; 071 public Instance (HttpServletRequest req, HttpServletResponse res) 072 { 073 request = req; 074 response = res; 075 try 076 { 077 output = res.getWriter(); 078 } 079 catch (IOException e) 080 { 081 e.printStackTrace(); 082 } 083 } 084 /** 085 * File name to be used when creating file. File will be 086 * suffixed with .csv. 087 * <p>Changes to this field can be made using the setValues method of the 088 * enclosing class. 089 * </p> 090 * @see #setOutputFilename(String) 091 * @see #getOutputFilename() 092 * @see CsvWriter#setValues(Instance) 093 * 094 */ 095 protected String outputFilename = "default"; 096 /** 097 * String to be placed in CSV file as the first line in the file. 098 * <p>Changes to this field can be made by using the setValues 099 * method of the enclosing class.</p> 100 * @see CsvWriter#setValues(Instance) 101 * 102 */ 103 protected String message = null; 104 /** 105 * Object containing database connection information. 106 * <p>This field is set by the makeConnection method.</p> 107 * @see CsvWriter#makeConnection() 108 */ 109 protected bradleyross.library.database.DatabaseProperties data = null; 110 /** 111 * Object containing the result set. 112 * <p>This object is created by the getResultSet method which 113 * is called by the service method.</p> 114 * @see CsvWriter#getResultSet(HttpServletRequest, DatabaseProperties) 115 */ 116 protected java.sql.ResultSet rs = null; 117 /** 118 * Object containing meta data on result set. 119 * <p>This object is created by the service method after the 120 * execution of the getResultSet method and before the 121 * execution of the printHeader method.</p> 122 * <p>Must be moved to inner class</p> 123 */ 124 protected java.sql.ResultSetMetaData rsmd = null; 125 /** 126 * Object representing the output to the HTTP response. 127 */ 128 protected java.io.PrintWriter output = null; 129 /** 130 * Number of columns in result set 131 */ 132 protected int columnCount; 133 /** 134 * Getter for rs. 135 * @return ResultSet object 136 * @see #rs 137 */ 138 public ResultSet getResultSet() 139 { 140 return rs; 141 } 142 /** 143 * Setter for rs. 144 * <p>This would not normally be called from outside classes.</p> 145 * @param value Value to be used for field 146 * @see #rs 147 */ 148 public void setResultSet(ResultSet value) 149 { 150 try 151 { 152 rs = value; 153 rsmd = rs.getMetaData(); 154 columnCount = rsmd.getColumnCount(); 155 } 156 catch (SQLException e) 157 { 158 159 e.printStackTrace(); 160 } 161 } 162 /** 163 * Getter for rsmd. 164 * 165 * @return ResultSetMetaData object 166 * @see #rsmd 167 */ 168 public ResultSetMetaData getResultSetMetaData() 169 { 170 return rsmd; 171 } 172 /** 173 * Setter for rsmd. 174 * 175 * <p>This would not normally be called by external classes.</p> 176 * 177 * @param value ResultSetMetaData object 178 * @see #rsmd 179 */ 180 public void setResultSetMetaData(ResultSetMetaData value) 181 { 182 rsmd = value; 183 } 184 public DatabaseProperties getDatabaseProperties() 185 { 186 return data; 187 } 188 public void setDatabaseProperties (DatabaseProperties value) 189 { 190 data = value; 191 } 192 public String getOutputFilename() 193 { 194 return outputFilename; 195 } 196 public void setOutputFilename(String value) 197 { 198 outputFilename = value; 199 } 200 /** 201 * Obtains number of columns in result set 202 * @return Number of columns 203 * @see #columnCount 204 */ 205 public int getColumnCount() 206 { 207 return columnCount; 208 } 209 /** 210 * Sets number of columns in result set. 211 * <p>This method would not normally be used.</p> 212 * @param value Value to be used 213 * @see #columnCount 214 */ 215 public void setColumnCount(int value) 216 { 217 columnCount = value; 218 } 219 /** 220 * Getter for output. 221 * @return PrintWriter object. 222 * @see #output 223 */ 224 public PrintWriter getPrintWriter() 225 { 226 return output; 227 } 228 /** 229 * Getter for message. 230 * 231 * @return Message text 232 * @see #message 233 */ 234 public String getMessage() 235 { 236 return message; 237 } 238 /** 239 * Getter for request. 240 * 241 * @return HttpServletRequest object 242 * @see #request 243 */ 244 public HttpServletRequest getRequest() 245 { 246 return request; 247 } 248 /** 249 * Getter for response. 250 * @return HttpServletResponse object 251 * @see #response 252 */ 253 public HttpServletResponse getResponse() 254 { 255 return response; 256 } 257 } 258 /** 259 * Called by servlet container when an HTTP transaction requests a response 260 * from the server. 261 * 262 * @param req Object containing request information 263 * @param res Object containing response information 264 */ 265 public void service (HttpServletRequest req, 266 HttpServletResponse res) throws IOException 267 { 268 Instance instance = new Instance(req, res); 269 PrintWriter output = instance.getPrintWriter(); 270 try 271 { 272 instance.setDatabaseProperties(makeConnection()); 273 instance.getDatabaseProperties().connect(); 274 ResultSet rs = getResultSet(req, instance.getDatabaseProperties()); 275 instance.setResultSet(rs); 276 setValues(instance); 277 res.setContentType("text/csv"); 278 res.setHeader("Content-Disposition", "attachment;filename=" + 279 instance.getOutputFilename() + ".csv"); 280 if (instance.getMessage() != null) 281 { output.print(instance.getMessage() + "\r\n"); } 282 if (rs == null) 283 { 284 output.print("No result set provided\r\n"); 285 return; 286 } 287 boolean moreRecords = rs.next(); 288 if (!moreRecords) 289 { 290 output.print("Result set is empty\r\n"); 291 return; 292 } 293 else 294 { printHeader(instance); } 295 while (true) 296 { 297 printLine(instance); 298 moreRecords = rs.next(); 299 if (!moreRecords) { break; } 300 } 301 instance.getResultSet().close(); 302 instance.getDatabaseProperties().close(); 303 } 304 catch (java.io.IOException e) 305 { throw new java.io.IOException("Error in processing file: " + 306 e.getClass().getName() + " : " + e.getMessage()); } 307 catch (java.sql.SQLException e) 308 { 309 System.out.println("SQL error in processing CSV file"); 310 throw new java.io.IOException("SQL error: " + e.getClass().getName() + 311 " : " + e.getMessage()); 312 } 313 } 314 /** 315 * Obtain the database connection information. 316 */ 317 protected abstract bradleyross.library.database.DatabaseProperties 318 makeConnection() throws IOException; 319 /** 320 * Will be overridden with the method for obtaining the SQL 321 * result set. 322 * <p>This should be kept separate from the getResultSet method within the 323 * Instance class. The {@link Instance#getResultSet() } method returns the 324 * ResultSet object stored in the Instance object. This method actually generates 325 * the ResultSet object. The service method then stores the result of this method 326 * in the Instance object.</p> 327 * @param req Request information 328 * @return Result set 329 * @see Instance#message 330 * @see Instance#outputFilename 331 */ 332 protected abstract java.sql.ResultSet getResultSet(HttpServletRequest req, DatabaseProperties data) 333 throws java.io.IOException; 334 /** 335 * Sets values in Instance object. 336 * <p>This must be overridden in a subclass in order to set parameters.</p> 337 * @param instance Instance object being affected 338 * @throws java.io.IOException 339 */ 340 protected void setValues(Instance instance) throws java.io.IOException 341 { ; } 342 /** 343 * Will print header line for CSV file. 344 */ 345 protected void printHeader (Instance instance) 346 throws java.io.IOException 347 { 348 ResultSetMetaData rsmd = instance.getResultSetMetaData(); 349 PrintWriter output = instance.getPrintWriter(); 350 try 351 { 352 for (int i = 1; i <= rsmd.getColumnCount(); i++) 353 { 354 if (i != 1) { output.print(","); } 355 output.print(rsmd.getColumnName(i)); 356 } 357 output.print("\r\n"); 358 } 359 catch (Exception e) 360 { throw new java.io.IOException("Error detected: " + e.getClass().getName() + 361 " : " + e.getMessage()); } 362 } 363 /** 364 * Will print one line of CSV file. 365 * <p>This method will be changed at a later time to allow the 366 * specification of overriding methods for formatting timestamps, 367 * integers, floating point numbers, character strings, etc.</p> 368 */ 369 protected void printLine (Instance instance) 370 throws java.io.IOException 371 { 372 int columnCount = 0; 373 ResultSet rs = instance.getResultSet(); 374 ResultSetMetaData rsmd = instance.getResultSetMetaData(); 375 PrintWriter output = instance.getPrintWriter(); 376 try 377 { 378 columnCount = rsmd.getColumnCount(); 379 } 380 catch (SQLException e1) 381 { 382 e1.printStackTrace(); 383 } 384 for (int i = 1; i <= columnCount; i++) 385 { 386 try 387 { 388 if (i != 1) { output.print(","); } 389 int columnType = rsmd.getColumnType(i); 390 if (columnType == Types.VARCHAR || columnType == Types.CHAR || 391 columnType == Types.LONGVARCHAR) 392 { 393 String working = rs.getString(i); 394 if (rs.wasNull()) { working = new String(); } 395 output.print(formatString(working, i)); 396 } 397 else if (columnType == Types.FLOAT || columnType == Types.REAL || 398 columnType == Types.DOUBLE) 399 { 400 double working = rs.getDouble(i); 401 if (rs.wasNull()) 402 { output.print(new String()); } 403 else 404 { output.print(formatDouble(working)); } 405 } 406 else if (columnType == Types.TINYINT || columnType == Types.SMALLINT || 407 columnType == Types.INTEGER || columnType == Types.BIGINT) 408 { 409 int working = rs.getInt(i); 410 if (rs.wasNull()) 411 { output.print(new String()); } 412 else 413 { output.print(formatInteger(working)); } 414 } 415 else if (columnType == Types.TIMESTAMP) 416 { 417 java.sql.Timestamp working = rs.getTimestamp(i); 418 if (rs.wasNull()) 419 { output.print(new String()); } 420 else 421 { 422 java.util.Date date = new java.util.Date(working.getTime()); 423 output.print(formatDateTime(date)); 424 } 425 } 426 else 427 /* 428 ** Haven't decided how to handle Types.DECIMAL and 429 ** TYPES.GENERIC. However, this may be academic as I don't 430 ** think that these will occur in the databases 431 ** that I will be using. 432 */ 433 { 434 String working = rs.getString(i); 435 if (rs.wasNull()) 436 { output.print(new String()); } 437 else 438 { output.print(working); } 439 } 440 } 441 catch (Exception e) 442 { throw new java.io.IOException("Error detected in column " + 443 Integer.toString(i) + ":" + 444 e.getClass().getName() + " : " + 445 e.getMessage()); } 446 } 447 try 448 { output.print("\r\n"); } 449 catch (Exception e) 450 { throw new java.io.IOException("Error detected: " + 451 e.getClass().getName() + " : " + 452 e.getMessage()); } 453 } 454 /** 455 * Method of calling formatString when it doesn't 456 * depend on the column in the result set. 457 * @param valueIn String to be processed 458 * @return Processed string 459 */ 460 protected final String formatString(String valueIn) 461 { return formatString(valueIn, -1); } 462 /** 463 * Double up on all double quotes in string and surround the string with 464 * double quotes so that it will be in the proper format for the CSV file. 465 * @param valueIn String to be processed 466 * @param column Column number of item - This will allow different formatting to be used 467 * for each column. This parameter is not used unless the method is 468 * overridden. 469 * @return Processed string 470 */ 471 protected String formatString(String valueIn, int column) 472 { 473 String working = null; 474 if (valueIn == null) 475 { working = " "; } 476 else if (valueIn.length() == 0) 477 { working = " "; } 478 else 479 { 480 java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\\""); 481 java.util.regex.Matcher matcher = pattern.matcher(valueIn); 482 working = matcher.replaceAll("\"\""); 483 } 484 if (working == null) { working = " "; } 485 return "\"" + working + "\""; 486 } 487 /** 488 * Alternate calling sequence, leaving out the column number. 489 * @param valueIn Floating point number to be formatted 490 * @return Formatted string 491 */ 492 protected final String formatDouble (double valueIn) 493 { return formatDouble (valueIn, -1); } 494 /** 495 * Formatter for floating point numbers. 496 * <p>This method can be overridden to control printing of 497 * the values in the CSV file.</p> 498 * @param valueIn Floating point number to be processed 499 * @param column Column number - This will allow different formatting to be used 500 * for each column. This parameter is not used unless the method 501 * is overridden. 502 * @return Formatted string 503 */ 504 protected String formatDouble (double valueIn, int column) 505 { return Double.toString(valueIn); } 506 /** 507 * Alternate calling sequence, omitting the column number. 508 * @param valueIn Integer value to be formatted 509 * @return Formatted string 510 */ 511 protected final String formatInteger (int valueIn) 512 { return formatInteger(valueIn, -1); } 513 /** 514 * Formatter for integer values. 515 * <p>This method can be overridden to control printing of 516 * the values in the CSV file.</p> 517 * @param valueIn Integer to be formatted 518 * @param column Column number - This will allow different formatting to be used 519 * for each column. This parameter is not used unless the method is 520 * overridden. 521 * @return Formatted string 522 */ 523 protected String formatInteger (int valueIn, int column) 524 { return Integer.toString(valueIn); } 525 /** 526 * Alternate calling sequence, leaving out the column number 527 * @param valueIn java.util.Date object containing date and time 528 * @return Formatted string 529 */ 530 protected final String formatDateTime (java.util.Date valueIn) 531 { return formatDateTime(valueIn, 1); } 532 /** 533 * Formatter for values involving date and time. 534 * <p>This method can be overridden to control printing of 535 * the values in the CSV file.</p> 536 * @param valueIn java.util.Date object containing date and time 537 * @param column Column number - This will allow different formatting to be used 538 * for each column. This parameter is not used unless the method is 539 * overridden. 540 * @return Formatted string 541 */ 542 protected String formatDateTime (java.util.Date valueIn, int column) 543 { 544 java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("MM/dd/yyyy HH:mm"); 545 return format.format(valueIn); 546 } 547}