001: /****************************************************************************
002: * DataSource.java
003: * ****************************************************************************/package org.openlaszlo.data;
004:
005: import java.io.*;
006: import java.net.MalformedURLException;
007: import java.util.HashMap;
008: import java.util.StringTokenizer;
009: import javax.servlet.http.HttpServletRequest;
010: import javax.servlet.http.HttpServletResponse;
011: import org.apache.commons.httpclient.URI;
012: import org.apache.commons.httpclient.URIException;
013: import org.apache.log4j.Logger;
014: import org.openlaszlo.utils.LZHttpUtils;
015: import org.openlaszlo.utils.FileUtils;
016: import org.openlaszlo.media.MimeType;
017:
018: /**
019: * Base class for server side LZX data/media sources.
020: */
021: abstract public class DataSource {
022: private static Logger mLogger = Logger.getLogger(DataSource.class);
023:
024: /**
025: * Get unique name of this data source.
026: *
027: * @return unique name of this data source.
028: */
029: public abstract String name();
030:
031: /**
032: * Get the data for this request.
033: *
034: * @return the data for this request.
035: * @param app absolute pathnane to app file.
036: * @param req request in progress.
037: * @param res response object.
038: * @param lastModifiedTime this is the timestamp on the currently cached
039: * item; this time can be used as the datasource sees fit (or ignored) in
040: * constructing the results. If the value is -1, assume there is no
041: * currently cached item.
042: *
043: * @throws DataSourceException if there was a problem with the data source.
044: * @throws IOException if there was a problem retrieving the data.
045: * @throws InterrupedIOException if there was a timeout retrieving the data.
046: */
047: public abstract Data getData(String app, HttpServletRequest req,
048: HttpServletResponse res, long lastModifiedTime)
049: throws InterruptedIOException, IOException,
050: DataSourceException;
051:
052: /**
053: * Determine the datasource from the incoming request,
054: * get the data, convert it to SWF, and write it out
055: * to the given response.
056: *
057: * @param app absolute path name to app requesting data
058: * @param req request
059: * @param res response
060: * @param converter converter to use
061: * @throws DataSourceException if the data source encounters an error
062: * @throws ConversionException if the conversion to SWF returns an error
063: * @throws IOException if there is an IO error
064: */
065: final public void getAsSWF(String app, HttpServletRequest req,
066: HttpServletResponse res, Converter converter)
067: throws DataSourceException, ConversionException,
068: IOException {
069:
070: Data data = null;
071: InputStream input = null;
072: OutputStream output = null;
073: long size = -1;
074: long since = -1;
075:
076: // Check to see if client side caching is on
077: boolean doClientCache = isClientCacheable(req);
078: if (doClientCache) {
079: String hdr = req.getHeader(LZHttpUtils.IF_MODIFIED_SINCE);
080: if (hdr != null) {
081: mLogger.debug(
082: /* (non-Javadoc)
083: * @i18n.test
084: * @org-mes="req last modified time: " + p[0]
085: */
086: org.openlaszlo.i18n.LaszloMessages.getMessage(
087: DataSource.class.getName(), "051018-96",
088: new Object[] { hdr }));
089: since = LZHttpUtils.getDate(hdr);
090: }
091: }
092:
093: mLogger.info(
094: /* (non-Javadoc)
095: * @i18n.test
096: * @org-mes="requesting URL: '" + p[0] + "'"
097: */
098: org.openlaszlo.i18n.LaszloMessages.getMessage(DataSource.class
099: .getName(), "051018-108", new Object[] { DataSource
100: .getURL(req) }));
101: try {
102: data = getData(app, req, res, since);
103:
104: if (data.notModified()) {
105: mLogger.info(
106: /* (non-Javadoc)
107: * @i18n.test
108: * @org-mes="NOT_MODIFIED"
109: */
110: org.openlaszlo.i18n.LaszloMessages.getMessage(
111: DataSource.class.getName(), "051018-120"));
112: res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
113: return;
114: }
115:
116: mLogger.debug("got data");
117:
118: if (!data.getMimeType().equals(MimeType.SWF)) {
119:
120: input = converter.convertToSWF(data, req, res);
121: size = input.available();
122: mLogger.debug(
123: /* (non-Javadoc)
124: * @i18n.test
125: * @org-mes="converted to " + p[0] + " bytes of SWF"
126: */
127: org.openlaszlo.i18n.LaszloMessages.getMessage(
128: DataSource.class.getName(), "051018-138",
129: new Object[] { new Long(size) }));
130: // FIXME: [2003-09-22 bloch] input.available not realiable
131:
132: String enc = converter.chooseEncoding(req);
133: if (enc != null && !enc.equals("")) {
134: input = converter.encode(req, input, enc);
135: res.setHeader(LZHttpUtils.CONTENT_ENCODING, enc);
136: size = input.available();
137: }
138:
139: } else {
140: mLogger.debug(
141: /* (non-Javadoc)
142: * @i18n.test
143: * @org-mes="remote content was SWF"
144: */
145: org.openlaszlo.i18n.LaszloMessages.getMessage(
146: DataSource.class.getName(), "051018-156"));
147: input = data.getInputStream();
148: size = data.size();
149: }
150:
151: if (size != -1) {
152: mLogger.debug(
153: /* (non-Javadoc)
154: * @i18n.test
155: * @org-mes="setting content length: " + p[0]
156: */
157: org.openlaszlo.i18n.LaszloMessages.getMessage(
158: DataSource.class.getName(), "051018-169",
159: new Object[] { new Long(size) }));
160: //res.setContentLength((int)size);
161: }
162:
163: if (doClientCache) {
164: long t = data.lastModified();
165: if (t != -1) {
166: res.setDateHeader(LZHttpUtils.LAST_MODIFIED, t);
167: }
168: } else {
169: LZHttpUtils.noStore(res);
170: }
171:
172: try {
173: output = res.getOutputStream();
174: long n = FileUtils.sendToStream(input, output);
175: mLogger.info(
176: /* (non-Javadoc)
177: * @i18n.test
178: * @org-mes=p[0] + " bytes sent"
179: */
180: org.openlaszlo.i18n.LaszloMessages.getMessage(
181: DataSource.class.getName(), "051018-192",
182: new Object[] { new Long(n) }));
183: } catch (FileUtils.StreamWritingException e) {
184: mLogger.warn(
185: /* (non-Javadoc)
186: * @i18n.test
187: * @org-mes="StreamWritingException while responding: " + p[0]
188: */
189: org.openlaszlo.i18n.LaszloMessages.getMessage(
190: DataSource.class.getName(), "051018-201",
191: new Object[] { e.getMessage() }));
192: }
193:
194: } finally {
195: if (data != null) {
196: data.release();
197: }
198: FileUtils.close(output);
199: FileUtils.close(input);
200: }
201: }
202:
203: /**
204: * Determine the datasource from the incoming request,
205: * get the data, and write it out
206: * to the given response.
207: *
208: * @param app pathname to app on disk
209: * @param req http request
210: * @param res http response
211: * @throws DataSourceException if the data source encounters an error
212: * @throws IOException if there is an IO error
213: */
214: final public void get(String app, HttpServletRequest req,
215: HttpServletResponse res) throws DataSourceException,
216: IOException {
217:
218: Data data = null;
219: InputStream input = null;
220: OutputStream output = null;
221: long size = -1;
222:
223: mLogger.info(
224: /* (non-Javadoc)
225: * @i18n.test
226: * @org-mes="requesting URL: '" + p[0] + "'"
227: */
228: org.openlaszlo.i18n.LaszloMessages.getMessage(DataSource.class
229: .getName(), "051018-241", new Object[] { DataSource
230: .getURL(req) }));
231:
232: try {
233:
234: data = getData(app, req, res, -1);
235:
236: input = data.getInputStream();
237: // FIXME: [2003-04-26 bloch] Reenable content-length header at some point.
238: // Client will sometimes bail in this situation:
239: // 1) set content length and bit client interrupts/closes socket before
240: // all content comes down.
241: // 2) next time the urls is hit, the client requests, server responds,
242: // but nothing shows in the browser. This seems to happen only
243: // when the content is coming from
244: // size = data.size();
245: size = -1;
246:
247: if (size != -1) {
248: mLogger.debug(
249: /* (non-Javadoc)
250: * @i18n.test
251: * @org-mes="setting content length: " + p[0]
252: */
253: org.openlaszlo.i18n.LaszloMessages.getMessage(
254: DataSource.class.getName(), "051018-266",
255: new Object[] { new Long(size) }));
256: res.setContentLength((int) size);
257: }
258:
259: // Hopefully back end tells the truth
260: res.setContentType(data.getMimeType());
261:
262: output = res.getOutputStream();
263: long n = FileUtils.send(input, output);
264: mLogger.info(
265: /* (non-Javadoc)
266: * @i18n.test
267: * @org-mes=p[0] + " bytes sent"
268: */
269: org.openlaszlo.i18n.LaszloMessages.getMessage(
270: DataSource.class.getName(), "051018-282",
271: new Object[] { new Long(n) }));
272:
273: } finally {
274: if (data != null) {
275: data.release();
276: }
277: FileUtils.close(output);
278: FileUtils.close(input);
279: }
280: }
281:
282: /**
283: * Return true if the request is marked cacheable on the client
284: */
285: final static public boolean isClientCacheable(HttpServletRequest req) {
286: String str = req.getParameter("ccache");
287: return (str != null && str.equals("true"));
288: }
289:
290: /**
291: * Get the actual URL for this request.
292: *
293: * @param req servlet request object.
294: * @param url the url string received from the client. Can contain
295: * "@WEBAPP@" string.
296: * @return the 'URL' for the request.
297: */
298: final static public String getURL(HttpServletRequest req,
299: String surl) throws MalformedURLException {
300: // "file:" is no longer supported, it is a security hole, make it into
301: // an "http" URL which points to the designated file.
302: if (surl.startsWith("file:")) {
303: String protocol = (req.isSecure() ? "https" : "http");
304: String host = req.getServerName();
305: int port = req.getServerPort();
306: String cp = req.getContextPath();
307:
308: // go past the "file:" prefix
309: String fpath = surl.substring(5);
310: String uri = req.getRequestURI();
311: int floc = uri.lastIndexOf("/");
312:
313: // for original url of "file:foo.xml, this constructs
314: // http://host:protocol/servlet-path/app-path/foo.xml
315: surl = protocol + "://" + host + ":" + port
316: + uri.substring(0, floc) + "/" + fpath;
317: }
318:
319: mLogger.debug(
320: /* (non-Javadoc)
321: * @i18n.test
322: * @org-mes="'url' is " + p[0]
323: */
324: org.openlaszlo.i18n.LaszloMessages.getMessage(DataSource.class
325: .getName(), "051018-339", new Object[] { surl }));
326: return LZHttpUtils.modifyWEBAPP(req, surl);
327: }
328:
329: /**
330: * Get the URL query paramter for this request.
331: *
332: * @param req servlet request object to retrieve URL parameter.
333: * @return the 'URL' for the request.
334: * @throws MalformedURLException if the url parameter is missing from the request.
335: */
336: static public String getURL(HttpServletRequest req)
337: throws MalformedURLException {
338:
339: String surl = req.getParameter("url");
340: if (surl == null) {
341: throw new MalformedURLException(
342: /* (non-Javadoc)
343: * @i18n.test
344: * @org-mes="'url' parameter missing from request"
345: */
346: org.openlaszlo.i18n.LaszloMessages.getMessage(
347: DataSource.class.getName(), "051018-362"));
348: }
349:
350: return getURL(req, surl);
351: }
352:
353: /**
354: * Utility function to get a hash map of query parameters from
355: * an url string.
356: *
357: * @param url string containing an URL to parse query parameters.
358: * @return hash map of query parameters, or an empty hash map if no query
359: * parameters exist.
360: */
361: final static public HashMap getQueryString(String url) {
362: HashMap map = new HashMap();
363: try {
364: URI uri = new URI(url.toCharArray());
365: String query = uri.getQuery();
366: if (query != null) {
367: StringTokenizer st = new StringTokenizer(query, "&");
368: while (st.hasMoreTokens()) {
369: String param = st.nextToken();
370: int i = param.indexOf("=");
371: if (i != -1) {
372: String k = param.substring(0, i);
373: String v = param.substring(i + 1, param
374: .length());
375: map.put(k, v);
376: }
377: }
378: }
379: } catch (URIException e) {
380: mLogger.debug(
381: /* (non-Javadoc)
382: * @i18n.test
383: * @org-mes="URIException: " + p[0]
384: */
385: org.openlaszlo.i18n.LaszloMessages.getMessage(
386: DataSource.class.getName(), "051018-403",
387: new Object[] { e.getMessage() }));
388: }
389: return map;
390: }
391: }
|