001package bradleyross.j2ee.servlets; 002import java.io.File; 003import java.io.IOException; 004import java.io.ByteArrayOutputStream; 005import java.io.FileWriter; 006import java.io.StringWriter; 007import java.io.PrintWriter; 008import java.util.Enumeration; 009import java.util.Date; 010import java.net.URLConnection; 011import javax.servlet.ServletConfig; 012import javax.servlet.ServletContext; 013import javax.servlet.ServletException; 014import javax.servlet.http.HttpServlet; 015import javax.servlet.http.HttpServletRequest; 016import javax.servlet.http.HttpServletResponse; 017import bradleyross.library.helpers.FileHelpers; 018/** 019 * Servlet for displaying a file based on the 020 * URL sent to the server. 021 * <ul> 022 * <li>The name of the file to be retrieved is obtained 023 * using {@link HttpServletRequest#getPathInfo()}.</li> 024 * <li>The MIME type is obtained from the file name using 025 * {@link URLConnection#guessContentTypeFromName(String)}</li> 026 * <li>The name of the INIT parameter containing the document 027 * root is given by the parameter <code>DocumentRootParameter</code>. 028 * If the parameter is not specified for the servlet, the default 029 * name is <code>DocumentRoot</code>.</li> 030 * </ul> 031 * <p>Consideration should be given to increasing security for the 032 * servlet. The first step would be to stop file names from 033 * containing two periods in a row.</p> 034 * @author Bradley Ross 035 * 036 */ 037public class GetFile extends HttpServlet 038{ 039 /** 040 * ID number to satisfy SERIALIZABLE interface. 041 */ 042 private static final long serialVersionUID = 1L; 043 /** 044 * Keeps track of how many instances of this servlet have 045 * been opened by the application server. 046 * 047 * <p>There is a problem in that Tomcat does not appear to 048 * treating this as a static value. The idea was that 049 * the application server could run multiple GetFile 050 * objects and would use this parameter to set the 051 * value of instanceNumber so that log files for the 052 * different objects would go to different files. Instead, 053 * the value appears to be zero each time the init 054 * method is called. The init method should only be called 055 * once for each GetFile object.</p> 056 */ 057 protected static int runningInstanceNumber = 0; 058 /** 059 * Identifies this instance of the servlet. 060 * 061 * <p>It was intended that this variable would identify the 062 * GetFile object if the application server was running 063 * multiple instances of the GetFile class.</p> 064 */ 065 protected int instanceNumber = 0; 066 /** 067 * Keeps track of the different instances of the service method that are running. 068 */ 069 protected int runningServiceNumber = 0; 070 /** 071 * ServletConfig object as passed to the init method. 072 */ 073 protected ServletConfig config = null; 074 /** 075 * ServletContext object containing information 076 * for the entire web application. 077 */ 078 protected ServletContext context = null; 079 /** 080 * Name of parameter containing the location of the document root. 081 */ 082 protected String fileRootParameter = "DocumentRoot"; 083 /** 084 * Name of root directory for reading files. 085 */ 086 protected String fileRoot = null; 087 /** 088 * Name of log file. 089 */ 090 protected String logFile = null; 091 /** 092 * Send messages to log file if true. 093 */ 094 protected boolean logFileActive = false; 095 /** 096 * Character string indicating end of line. 097 */ 098 protected String lineSeparator = null; 099 /** 100 * Servlet has a valid configuration when true. 101 */ 102 protected boolean valid = true; 103 /** 104 * Run when object is created for processing calls to servlet. 105 * 106 * <p>The application server can create 107 * multiple objects for a single servlet to improve 108 * performance.</p> 109 */ 110 public void init(ServletConfig configIn) throws ServletException 111 { 112 super.init(configIn); 113 runningInstanceNumber++; 114 instanceNumber = runningInstanceNumber; 115 config = configIn; 116 context = config.getServletContext(); 117 try 118 { 119 lineSeparator = System.getProperty("line.separator"); 120 } 121 catch (Exception e) 122 { 123 lineSeparator = "\n"; 124 } 125 String working = config.getInitParameter("DocumentRootParameter"); 126 if (working != null) 127 { fileRootParameter = working; } 128 working = (String) null; 129 working = config.getInitParameter(fileRootParameter); 130 if (working != null) 131 { fileRoot = working; } 132 else 133 { 134 working = null; 135 working = context.getInitParameter(fileRootParameter); 136 if (working != null) 137 { 138 fileRoot = working; 139 } 140 } 141 if (fileRoot == null) 142 { valid = false; } 143 else 144 { 145 if (fileRoot.endsWith("/") || fileRoot.endsWith("\\")) 146 { 147 fileRoot = fileRoot.substring(0, fileRoot.length() - 1); 148 } 149 } 150 151 working = (String) null; 152 working = config.getInitParameter("LogFile"); 153 if (working == null) 154 { 155 logFileActive = false; 156 } 157 else 158 { 159 logFile = working + Integer.toString(instanceNumber) + ".txt"; 160 runningServiceNumber = 0; 161 logFileActive = true; 162 createLogEntry("Instance of servlet has been created"); 163 } 164 } 165 /** 166 * Run when object for processing servlet is no longer needed 167 * and is destroyed. 168 */ 169 public void destroy() 170 { 171 createLogEntry("Instance has been destroyed"); 172 super.destroy(); 173 } 174 /** 175 * Place a record in the log file. 176 * @param message Text of message 177 */ 178 protected void createLogEntry(String message) 179 { 180 if (!logFileActive) 181 { 182 return; 183 } 184 boolean success = false; 185 for (int i = 0; i < 3; i++) 186 { 187 try 188 { 189 FileWriter output = new FileWriter(logFile, true); 190 output.write(new Date().toString() + " "); 191 output.write(message); 192 output.write(lineSeparator); 193 output.close(); 194 success = true; 195 } 196 catch (IOException e) 197 { 198 success = false; 199 } 200 if (success) { break; } 201 } 202 } 203 /** 204 * Place a record in the log file. 205 * @param message Text of message 206 * @param serviceNumber Sequence number for servlet instance 207 */ 208 protected void createLogEntry(String message, int serviceNumber) 209 { 210 if (!logFileActive) 211 { 212 return; 213 } 214 boolean success = false; 215 for (int i = 0; i < 3; i++) 216 { 217 try 218 { 219 FileWriter output = new FileWriter(logFile, true); 220 output.write(new Date().toString() + " " + 221 Integer.toString(serviceNumber) + " " + 222 message + lineSeparator); 223 output.close(); 224 success = true; 225 } 226 catch (IOException e) 227 { 228 success = false; 229 } 230 if (success) { break; } 231 } 232 } 233 /** 234 * Place a message in the log file. 235 * @param message Text of message 236 * @param e Exception for which message is generated 237 * @param serviceNumber Sequence number for servlet instance 238 */ 239 protected void createLogEntry(String message, Throwable e, int serviceNumber) 240 { 241 if (!logFileActive) 242 { 243 return; 244 } 245 boolean success = false; 246 for (int i = 0; i < 3; i++) 247 { 248 try 249 { 250 FileWriter output = new FileWriter(logFile, true); 251 StringWriter working = new StringWriter(); 252 PrintWriter writer = new PrintWriter(working); 253 e.printStackTrace(writer); 254 output.write(new Date().toString() + " " + Integer.toString(serviceNumber) + 255 " " + message + lineSeparator + working.toString()); 256 output.close(); 257 success = true; 258 } 259 catch (IOException e1) 260 { 261 success = false; 262 } 263 if (success) { break; } 264 } 265 } 266 /** 267 * Processes request to a servlet. 268 * 269 * <p>There can be many instances of the service method running at 270 * the same time. This must be taken into account when coding this 271 * method.</p> 272 * 273 * <p>It may be necessary to change this file so that the information 274 * is recorded to a byte array and then repeated as necessary until 275 * success is achieved.</p> 276 * 277 * @param req Object containing request information 278 * @param res Object containing response information 279 * @see HttpServletRequest 280 * @see HttpServletResponse 281 * @see IOException 282 */ 283// @SuppressWarnings("unchecked") 284 public void service (HttpServletRequest req, 285 HttpServletResponse res) throws IOException 286 { 287 if (!valid) 288 { 289 res.sendError(500, "Invalid initialization parameters for servlet"); 290 } 291 runningServiceNumber++; 292 if (runningServiceNumber > 9999) {runningServiceNumber = 0; } 293 int serviceNumber = runningServiceNumber; 294 /** 295 * Object representing the output to the HTTP response when 296 * processing a text file. 297 */ 298 java.io.PrintWriter output = null; 299 /** 300 * Object representing the output to the HTTP response when 301 * processing a binary file. 302 */ 303 java.io.OutputStream stream = null; 304 String fullFileName = null; 305 String suffix = null; 306 String contextPath = null; 307 String servletPath = null; 308 String pathInfo = null; 309 String queryString = null; 310 String mimeType = null; 311 try 312 { 313 contextPath = req.getContextPath(); 314 pathInfo = req.getPathInfo(); 315 servletPath = req.getServletPath(); 316 queryString = req.getQueryString(); 317 if (pathInfo == null) 318 { 319 res.sendError(500, "No path info in request"); 320 return; 321 } 322 mimeType = URLConnection.guessContentTypeFromName(pathInfo); 323 createLogEntry("Running GetFile servlet for " + pathInfo + " with MIME type of " + 324 mimeType, serviceNumber); 325 if ((System.getProperty("file.separator")).equals("\\")) 326 { 327 pathInfo = pathInfo.replaceAll("/", "\\\\"); 328 } 329 fullFileName = fileRoot.concat(pathInfo); 330 int position = pathInfo.lastIndexOf("."); 331 if (position >= 0 && position < (pathInfo.length() - 1)) 332 { 333 suffix = pathInfo.substring(position + 1).toUpperCase(); 334 } 335 if (queryString == null) 336 { ; } 337 else if (queryString.toUpperCase().startsWith("QUERY")) 338 { 339 Enumeration<String> initParameterNames = config.getInitParameterNames(); 340 Enumeration<String> parameterNames = initParameterNames; 341 output = res.getWriter(); 342 res.setContentType("text/plain"); 343 output.println("context path: " + contextPath); 344 output.println("servlet path: " + servletPath); 345 output.println("path info: " + pathInfo ); 346 output.println("query string: " + queryString); 347 while (parameterNames.hasMoreElements()) 348 { 349 String name = parameterNames.nextElement(); 350 output.println(name + " : " + config.getInitParameter(name)); 351 } 352 output.println("Suffix: " + suffix); 353 output.println("Full file name: " + fullFileName); 354 File tester = new File(fullFileName); 355 output.println("***"); 356 output.println("Does file exist: " + Boolean.toString(tester.exists())); 357 if (tester.exists()) 358 { output.println("File exists"); } 359 else 360 { output.println("File does not exist"); } 361 output.println("***"); 362 output.flush(); 363 return; 364 } 365 if (!(new File(fullFileName)).exists()) 366 { 367 /* 368 * If file does not exist, retry a second later. 369 */ 370 boolean success = false; 371 for (int i = 0; i < 3; i++) 372 { 373 try 374 { 375 Thread.sleep(1000l * (long) (i + 1)); 376 } 377 catch (InterruptedException e) 378 { ; } 379 if (new File(fullFileName).exists()) 380 { 381 success = true; 382 break; 383 } 384 else 385 { 386 createLogEntry("File " + fullFileName + " did not exist", serviceNumber); 387 } 388 } 389 if (!success) 390 { 391 createLogEntry("File " + fullFileName + " does not exist: Aborting", serviceNumber); 392 res.sendError(500, "File " + fullFileName + " does not exist"); 393 return; 394 } 395 } 396 if (suffix == null) 397 { 398 createLogEntry("No suffix found"); 399 res.sendError(500, "No suffix found"); 400 return; 401 } 402 else if (suffix.equals("TXT")) 403 { 404 output = res.getWriter(); 405 res.setContentType("text/plain"); 406 output.print(FileHelpers.readTextFile(fullFileName)); 407 return; 408 } 409 else if (suffix.equalsIgnoreCase("PNG") || 410 suffix.equalsIgnoreCase("JPG") || 411 suffix.equalsIgnoreCase("JPEG") || 412 suffix.equalsIgnoreCase("GIF") ) 413 { 414 stream = res.getOutputStream(); 415 res.setContentType(mimeType); 416 ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); 417 byte[] createdArray = null; 418 boolean success = false; 419 for (int trial = 0; trial < 5; trial++) 420 { 421 try 422 { 423 File newOutputFile = new File(fullFileName); 424 FileHelpers.getBytes( 425 newOutputFile, 426 byteArray); 427 createdArray = byteArray.toByteArray(); 428 createLogEntry("Length of " + fullFileName + " is " + 429 Integer.toString(createdArray.length), serviceNumber); 430 res.setContentLength(createdArray.length); 431 byteArray.writeTo(stream); 432 success = true; 433 } 434 catch (Exception e) 435 { 436 success = false; 437 byteArray.reset(); 438 if (trial < 4) 439 { 440 createLogEntry(new Date().toString() + 441 " Warning: error in GetFile.service reading " + fullFileName + " - " + 442 e.getClass().getName() + " " + e.getMessage() + " - " + 443 "Retrying " + Integer.toString(trial), e, serviceNumber); 444 } 445 else 446 { 447 createLogEntry(new Date().toString() + 448 " Warning: error in GetFile.service reading " + fullFileName + " - " + 449 e.getClass().getName() + " " + e.getMessage() + " - " + 450 "Aborting", e, serviceNumber); 451 throw new IOException(e.getClass().getName() + " " + e.getMessage()); 452 } 453 } 454 if (success) 455 { 456 createLogEntry("Processing of " + fullFileName + " complete", serviceNumber); 457 break; 458 } 459 } 460 if (!success) 461 { 462 createLogEntry("Failure in processing " + fullFileName, serviceNumber); 463 res.sendError(500, "Unable to read" + fullFileName); 464 } 465 return; 466 } 467 else 468 { 469 output = res.getWriter(); 470 res.setContentType("text/plain"); 471 output.println("Suffix " + suffix + 472 " does not have a handler defined"); 473 createLogEntry ("File " + fullFileName + 474 " does not have a handler defined", serviceNumber); 475 res.sendError(500, "File " + fullFileName + 476 " does not have a handler defined"); 477 } 478 } 479 catch (java.io.IOException e) 480 { 481 createLogEntry("Failure in servlet: " + 482 e.getClass().getName() + " " + 483 e.getMessage(), e, serviceNumber); 484 res.sendError(500, "Internal error in servlet: " + e.getClass().getName() + " " + 485 e.getMessage()); 486 } 487 } 488 /* End of service method */ 489 /** 490 * This provides an error message if it is attempted to run this 491 * class as a Java application rather than a Java servlet. 492 * 493 * @param args Not used at this time 494 */ 495 public static void main(String[] args) 496 { 497 System.out.println("No main program provided."); 498 } 499}