KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jnlp > sample > servlet > JnlpFileHandler


1 /*
2  * @(#)JnlpFileHandler.java 1.12 05/11/17
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * -Redistribution of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  *
12  * -Redistribution in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of contributors may
17  * be used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
22  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
24  * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
25  * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
26  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
27  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
28  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
29  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31  *
32  * You acknowledge that this software is not designed, licensed or intended
33  * for use in the design, construction, operation or maintenance of any
34  * nuclear facility.
35  */

36
37 package jnlp.sample.servlet;
38
39 import java.util.*;
40 import java.util.regex.*;
41 import java.net.*;
42 import java.io.*;
43 import javax.servlet.*;
44 import javax.servlet.http.*;
45 import javax.xml.parsers.*;
46 import org.xml.sax.*;
47 import javax.xml.transform.*;
48 import javax.xml.transform.dom.DOMSource JavaDoc;
49 import javax.xml.transform.stream.StreamResult JavaDoc;
50 import org.w3c.dom.*;
51
52 /* The JNLP file handler implements a class that keeps
53  * track of JNLP files and their specializations
54  */

55 public class JnlpFileHandler {
56     private static final String JavaDoc JNLP_MIME_TYPE = "application/x-java-jnlp-file";
57     private static final String JavaDoc HEADER_LASTMOD = "Last-Modified";
58     
59     private ServletContext _servletContext;
60     private Logger _log = null;
61     private HashMap _jnlpFiles = null;
62     
63     /** Initialize JnlpFileHandler for the specific ServletContext */
64     public JnlpFileHandler(ServletContext servletContext, Logger log) {
65         _servletContext = servletContext;
66         _log = log;
67         _jnlpFiles = new HashMap();
68     }
69     
70     private static class JnlpFileEntry {
71         // Response
72
DownloadResponse _response;
73         // Keeps track of cache is out of date
74
private long _lastModified;
75         
76         // Constructor
77
JnlpFileEntry(DownloadResponse response, long lastmodfied) {
78             _response = response;
79             _lastModified = lastmodfied;
80         }
81         
82         public DownloadResponse getResponse() { return _response; }
83         long getLastModified() { return _lastModified; }
84     }
85     
86     /* Main method to lookup an entry */
87     public synchronized DownloadResponse getJnlpFile(JnlpResource jnlpres, DownloadRequest dreq)
88     throws IOException {
89     String JavaDoc path = jnlpres.getPath();
90     URL resource = jnlpres.getResource();
91     long lastModified = jnlpres.getLastModified();
92     
93     
94     _log.addDebug("lastModified: " + lastModified + " " + new Date(lastModified));
95     if (lastModified == 0) {
96         _log.addWarning("servlet.log.warning.nolastmodified", path);
97     }
98     
99     // fix for 4474854: use the request URL as key to look up jnlp file
100
// in hash map
101
String JavaDoc reqUrl = HttpUtils.getRequestURL(dreq.getHttpRequest()).toString();
102
103     // Check if entry already exist in HashMap
104
JnlpFileEntry jnlpFile = (JnlpFileEntry)_jnlpFiles.get(reqUrl);
105
106     if (jnlpFile != null && jnlpFile.getLastModified() == lastModified) {
107         // Entry found in cache, so return it
108
return jnlpFile.getResponse();
109     }
110     
111     // Read information from WAR file
112
long timeStamp = lastModified;
113     String JavaDoc mimeType = _servletContext.getMimeType(path);
114     if (mimeType == null) mimeType = JNLP_MIME_TYPE;
115     
116     StringBuffer JavaDoc jnlpFileTemplate = new StringBuffer JavaDoc();
117     URLConnection conn = resource.openConnection();
118     BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
119     String JavaDoc line = br.readLine();
120     if (line != null && line.startsWith("TS:")) {
121         timeStamp = parseTimeStamp(line.substring(3));
122         _log.addDebug("Timestamp: " + timeStamp + " " + new Date(timeStamp));
123         if (timeStamp == 0) {
124         _log.addWarning("servlet.log.warning.notimestamp", path);
125         timeStamp = lastModified;
126         }
127         line = br.readLine();
128     }
129     while(line != null) {
130         jnlpFileTemplate.append(line);
131         line = br.readLine();
132     }
133     
134     String JavaDoc jnlpFileContent = specializeJnlpTemplate(dreq.getHttpRequest(), path, jnlpFileTemplate.toString());
135     
136     // Convert to bytes as a UTF-8 encoding
137
byte[] byteContent = jnlpFileContent.getBytes("UTF-8");
138     
139     // Create entry
140
DownloadResponse resp = DownloadResponse.getFileDownloadResponse(byteContent,
141                                      mimeType,
142                                      timeStamp,
143                                      jnlpres.getReturnVersionId());
144     jnlpFile = new JnlpFileEntry(resp, lastModified);
145     _jnlpFiles.put(reqUrl, jnlpFile);
146     
147     return resp;
148     }
149
150     /* Main method to lookup an entry (NEW for JavaWebStart 1.5+) */
151     public synchronized DownloadResponse getJnlpFileEx(JnlpResource jnlpres, DownloadRequest dreq)
152     throws IOException {
153         String JavaDoc path = jnlpres.getPath();
154         URL resource = jnlpres.getResource();
155         long lastModified = jnlpres.getLastModified();
156         
157         
158         _log.addDebug("lastModified: " + lastModified + " " + new Date(lastModified));
159         if (lastModified == 0) {
160             _log.addWarning("servlet.log.warning.nolastmodified", path);
161         }
162         
163         // fix for 4474854: use the request URL as key to look up jnlp file
164
// in hash map
165
String JavaDoc reqUrl = HttpUtils.getRequestURL(dreq.getHttpRequest()).toString();
166         // SQE: To support query string, we changed the hash key from Request URL to (Request URL + query string)
167
if (dreq.getQuery() != null)
168             reqUrl += dreq.getQuery();
169         
170         // Check if entry already exist in HashMap
171
JnlpFileEntry jnlpFile = (JnlpFileEntry)_jnlpFiles.get(reqUrl);
172         
173         if (jnlpFile != null && jnlpFile.getLastModified() == lastModified) {
174             // Entry found in cache, so return it
175
return jnlpFile.getResponse();
176         }
177         
178         // Read information from WAR file
179
long timeStamp = lastModified;
180         String JavaDoc mimeType = _servletContext.getMimeType(path);
181         if (mimeType == null) mimeType = JNLP_MIME_TYPE;
182         
183         StringBuffer JavaDoc jnlpFileTemplate = new StringBuffer JavaDoc();
184         URLConnection conn = resource.openConnection();
185         BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
186         String JavaDoc line = br.readLine();
187         if (line != null && line.startsWith("TS:")) {
188             timeStamp = parseTimeStamp(line.substring(3));
189             _log.addDebug("Timestamp: " + timeStamp + " " + new Date(timeStamp));
190             if (timeStamp == 0) {
191                 _log.addWarning("servlet.log.warning.notimestamp", path);
192                 timeStamp = lastModified;
193             }
194             line = br.readLine();
195         }
196         while(line != null) {
197             jnlpFileTemplate.append(line);
198             line = br.readLine();
199         }
200         
201         String JavaDoc jnlpFileContent = specializeJnlpTemplate(dreq.getHttpRequest(), path, jnlpFileTemplate.toString());
202         
203     /* SQE: We need to add query string back to href in jnlp file. We also need to handle JRE requirement for
204      * the test. We reconstruct the xml DOM object, modify the value, then regenerate the jnlpFileContent.
205      */

206         String JavaDoc query = dreq.getQuery();
207         String JavaDoc testJRE = dreq.getTestJRE();
208         _log.addDebug("Double check query string: " + query);
209         // For backward compatibility: Always check if the href value exists.
210
// Bug 4939273: We will retain the jnlp template structure and will NOT add href value. Above old
211
// approach to always check href value caused some test case not run.
212
if (query != null) {
213             byte [] cb = jnlpFileContent.getBytes("UTF-8");
214             ByteArrayInputStream bis = new ByteArrayInputStream(cb);
215             try {
216                 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
217                 DocumentBuilder builder = factory.newDocumentBuilder();
218                 Document document = builder.parse(bis);
219                 if (document != null && document.getNodeType() == Node.DOCUMENT_NODE) {
220                     boolean modified = false;
221                     Element root = document.getDocumentElement();
222                   
223                     if (root.hasAttribute("href") && query != null) {
224                         String JavaDoc href = root.getAttribute("href");
225                         root.setAttribute("href", href + "?" + query);
226                         modified = true;
227                     }
228                     // Update version value for j2se tag
229
if (testJRE != null) {
230                         NodeList j2seNL = root.getElementsByTagName("j2se");
231                         if (j2seNL != null) {
232                             Element j2se = (Element) j2seNL.item(0);
233                             String JavaDoc ver = j2se.getAttribute("version");
234                             if (ver.length() > 0) {
235                                 j2se.setAttribute("version", testJRE);
236                                 modified = true;
237                             }
238                         }
239                     }
240                     TransformerFactory tFactory = TransformerFactory.newInstance();
241                     Transformer transformer = tFactory.newTransformer();
242                     DOMSource JavaDoc source = new DOMSource JavaDoc(document);
243                     StringWriter sw = new StringWriter();
244                     StreamResult JavaDoc result = new StreamResult JavaDoc(sw);
245                     transformer.transform(source, result);
246                     jnlpFileContent = sw.toString();
247                     _log.addDebug("Converted jnlpFileContent: " + jnlpFileContent);
248                     // Since we modified the file on the fly, we always update the timestamp value with current time
249
if (modified) {
250                         timeStamp = new java.util.Date JavaDoc().getTime();
251                         _log.addDebug("Last modified on the fly: " + timeStamp);
252                     }
253                 }
254             } catch (Exception JavaDoc e) {
255                 _log.addDebug(e.toString(), e);
256             }
257         }
258         
259         // Convert to bytes as a UTF-8 encoding
260
byte[] byteContent = jnlpFileContent.getBytes("UTF-8");
261         
262         // Create entry
263
DownloadResponse resp = DownloadResponse.getFileDownloadResponse(byteContent,
264                                      mimeType,
265                                      timeStamp,
266                                      jnlpres.getReturnVersionId());
267         jnlpFile = new JnlpFileEntry(resp, lastModified);
268         _jnlpFiles.put(reqUrl, jnlpFile);
269         
270         return resp;
271     }
272     
273     /* This method performs the following substituations
274      * $$name
275      * $$codebase
276      * $$context
277      */

278     private String JavaDoc specializeJnlpTemplate(HttpServletRequest request, String JavaDoc respath, String JavaDoc jnlpTemplate) {
279         String JavaDoc urlprefix = getUrlPrefix(request);
280         int idx = respath.lastIndexOf('/'); //
281
String JavaDoc name = respath.substring(idx + 1); // Exclude /
282
String JavaDoc codebase = respath.substring(0, idx + 1); // Include /
283
jnlpTemplate = substitute(jnlpTemplate, "$$name", name);
284     // fix for 5039951: Add $$hostname macro
285
jnlpTemplate = substitute(jnlpTemplate, "$$hostname",
286                   request.getServerName());
287         jnlpTemplate = substitute(jnlpTemplate, "$$codebase", urlprefix + request.getContextPath() + codebase);
288         jnlpTemplate = substitute(jnlpTemplate, "$$context", urlprefix + request.getContextPath());
289         // fix for 6256326: add $$site macro to sample jnlp servlet
290
jnlpTemplate = substitute(jnlpTemplate, "$$site", urlprefix);
291         return jnlpTemplate;
292     }
293     
294     // This code is heavily inspired by the stuff in HttpUtils.getRequestURL
295
private String JavaDoc getUrlPrefix(HttpServletRequest req) {
296         StringBuffer JavaDoc url = new StringBuffer JavaDoc();
297         String JavaDoc scheme = req.getScheme();
298         int port = req.getServerPort();
299         url.append(scheme); // http, https
300
url.append("://");
301         url.append(req.getServerName());
302         if ((scheme.equals("http") && port != 80)
303         || (scheme.equals("https") && port != 443)) {
304             url.append(':');
305             url.append(req.getServerPort());
306         }
307         return url.toString();
308     }
309     
310     private String JavaDoc substitute(String JavaDoc target, String JavaDoc key, String JavaDoc value) {
311         int start = 0;
312         do {
313             int idx = target.indexOf(key, start);
314             if (idx == -1) return target;
315             target = target.substring(0, idx) + value + target.substring(idx + key.length());
316             start = idx + value.length();
317         } while(true);
318     }
319     
320     /** Parses a ISO 8601 Timestamp. The format of the timestamp is:
321      *
322      * YYYY-MM-DD hh:mm:ss or YYYYMMDDhhmmss
323      *
324      * Hours (hh) is in 24h format. ss are optional. Time are by default relative
325      * to the current timezone. Timezone information can be specified
326      * by:
327      *
328      * - Appending a 'Z', e.g., 2001-12-19 12:00Z
329      * - Appending +hh:mm, +hhmm, +hh, -hh:mm -hhmm, -hh to
330      * indicate that the locale timezone used is either the specified
331      * amound before or after GMT. For example,
332      *
333      * 12:00Z = 13:00+1:00 = 0700-0500
334      *
335      * The method returns 0 if it cannot pass the string. Otherwise, it is
336      * the number of milliseconds size sometime in 1969.
337      */

338     private long parseTimeStamp(String JavaDoc timestamp) {
339         int YYYY = 0;
340         int MM = 0;
341         int DD = 0;
342         int hh = 0;
343         int mm = 0;
344         int ss = 0;
345         
346         timestamp = timestamp.trim();
347         try {
348             // Check what format is used
349
if (matchPattern("####-##-## ##:##", timestamp)) {
350                 YYYY = getIntValue(timestamp, 0, 4);
351                 MM = getIntValue(timestamp, 5, 7);
352                 DD = getIntValue(timestamp, 8, 10);
353                 hh = getIntValue(timestamp, 11, 13);
354                 mm = getIntValue(timestamp, 14, 16);
355                 timestamp = timestamp.substring(16);
356                 if (matchPattern(":##", timestamp)) {
357                     ss = getIntValue(timestamp, 1, 3);
358                     timestamp = timestamp.substring(3);
359                 }
360             } else if (matchPattern("############", timestamp)) {
361                 YYYY = getIntValue(timestamp, 0, 4);
362                 MM = getIntValue(timestamp, 4, 6);
363                 DD = getIntValue(timestamp, 6, 8);
364                 hh = getIntValue(timestamp, 8, 10);
365                 mm = getIntValue(timestamp, 10, 12);
366                 timestamp = timestamp.substring(12);
367                 if (matchPattern("##", timestamp)) {
368                     ss = getIntValue(timestamp, 0, 2);
369                     timestamp = timestamp.substring(2);
370                 }
371             } else {
372                 // Unknown format
373
return 0;
374             }
375         } catch(NumberFormatException JavaDoc e) {
376             // Bad number
377
return 0;
378         }
379         
380         String JavaDoc timezone = null;
381         // Remove timezone information
382
timestamp = timestamp.trim();
383         if (timestamp.equalsIgnoreCase("Z")) {
384             timezone ="GMT";
385         } else if (timestamp.startsWith("+") || timestamp.startsWith("-")) {
386             timezone = "GMT" + timestamp;
387         }
388         
389         if (timezone == null) {
390             // Date is relative to current locale
391
Calendar cal = Calendar.getInstance();
392             cal.set(YYYY, MM - 1, DD, hh, mm, ss);
393             return cal.getTime().getTime();
394         } else {
395             // Date is relative to a timezone
396
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
397             cal.set(YYYY, MM - 1, DD, hh, mm, ss);
398             return cal.getTime().getTime();
399         }
400     }
401     
402     private int getIntValue(String JavaDoc key, int start, int end) {
403         return Integer.parseInt(key.substring(start, end));
404     }
405     
406     private boolean matchPattern(String JavaDoc pattern, String JavaDoc key) {
407         // Key must be longer than pattern
408
if (key.length() < pattern.length()) return false;
409         for(int i = 0; i < pattern.length(); i++) {
410             char format = pattern.charAt(i);
411             char ch = key.charAt(i);
412             if (!((format == '#' && Character.isDigit(ch)) || (format == ch))) {
413                 return false;
414             }
415         }
416         return true;
417     }
418 }
419
420
421
Popular Tags