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}