View Javadoc

1   /*
2    * This file is part of Domingo
3    * an Open Source Java-API to Lotus Notes/Domino
4    * hosted at http://domingo.sourceforge.net
5    *
6    * Copyright (c) 2003-2007 Beck et al. projects GmbH Munich, Germany (http://www.bea.de)
7    *
8    * This library is free software; you can redistribute it and/or
9    * modify it under the terms of the GNU Lesser General Public
10   * License as published by the Free Software Foundation; either
11   * version 2.1 of the License, or (at your option) any later version.
12   *
13   * This library is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   * Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public
19   * License along with this library; if not, write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21   */
22  
23  package de.bea.domingo.http;
24  
25  import java.io.BufferedReader;
26  import java.io.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.io.InputStreamReader;
29  import java.nio.charset.Charset;
30  import java.text.SimpleDateFormat;
31  import java.util.ArrayList;
32  import java.util.Calendar;
33  import java.util.GregorianCalendar;
34  import java.util.Iterator;
35  import java.util.List;
36  
37  import org.apache.commons.httpclient.Header;
38  import org.apache.commons.httpclient.HttpStatus;
39  import org.xml.sax.Attributes;
40  import org.xml.sax.SAXException;
41  import org.xml.sax.SAXParseException;
42  import org.xml.sax.helpers.DefaultHandler;
43  
44  import de.bea.domingo.DBase;
45  import de.bea.domingo.DNotesMonitor;
46  import de.bea.domingo.monitor.AbstractMonitorEnabled;
47  
48  /***
49   * Abstract base class for all implementations of interfaces derived from
50   * <code>DBase</code>.
51   *
52   * @author <a href=mailto:kriede@users.sourceforge.net>Kurt Riede</a>
53   */
54  public abstract class BaseHttp extends AbstractMonitorEnabled implements DBase {
55  
56      ////////////////////////////////////////////////
57      // constants
58      ////////////////////////////////////////////////
59  
60      private static final int VIEW_ENTRY_TIME_LENGTH = "ThhMMss".length();
61  
62      private static final int VIEW_ENTRY_DATE_LENGTH = "yyyymmdd".length();
63  
64      private static final String LINE_TERM = System.getProperty("line.separator");
65  
66      /*** Number of characters needed to represent a date/time value. */
67      protected static final int DATETIME_STRING_LENGTH = 20;
68  
69      /*** Maximum number of items supported in method getItemValues(). */
70      protected static final int NUM_DATETIME_VALUES = 1000;
71  
72      /*** maximum number of characters to parse in method getItemValues(). */
73      protected static final int MAX_DATETIME_LENGTH = NUM_DATETIME_VALUES * DATETIME_STRING_LENGTH;
74  
75      ////////////////////////////////////////////////
76      // instance attributes
77      ////////////////////////////////////////////////
78  
79      /*** Reference to the parent object. */
80      private final DBase fParent;
81  
82      /*** Reference to the factory which controls this instance. */
83      private final NotesHttpFactory fFactory;
84  
85      private static final SimpleDateFormat XML_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss,SS");
86      // TODO add time zone offset in hours (e.g. "+01")
87  
88      ////////////////////////////////////////////////
89      // creation
90      ////////////////////////////////////////////////
91  
92      /***
93       * Creates a new DBaseImpl object.
94       *
95       * @param theFactory the controlling factory
96       * @param theParent the parent object
97       * @param monitor the monitor
98       */
99      protected BaseHttp(final NotesHttpFactory theFactory, final DBase theParent,
100                         final DNotesMonitor monitor) {
101         super(monitor);
102         this.fFactory = theFactory;
103         this.fParent = theParent;
104     }
105 
106     ////////////////////////////////////////////////
107     // protected utility methods for sub classes
108     ////////////////////////////////////////////////
109 
110     /***
111      * Returns the parent object.
112      *
113      * @return the parent object or <code>null</code> if no parent available
114      */
115     protected final BaseHttp getParent() {
116         return (BaseHttp) fParent;
117     }
118 
119     /***
120      * Returns the parent object.
121      *
122      * @return the parent object or <code>null</code> if no parent available
123      */
124     /***
125      * Returns the Domingo session that created the current object.
126      *
127      * @return Domingo session of current object
128      */
129     protected final SessionHttp getDSession() {
130         BaseHttp base = this;
131         while (!(base instanceof SessionHttp)) {
132             base = (BaseHttp) base.getParent();
133         }
134         return (SessionHttp) base;
135     }
136 
137     /***
138      * Returns the factory corresponding to this instance.
139      *
140      * @return the corresponding factory
141      */
142     protected final NotesHttpFactory getFactory() {
143         return fFactory;
144     }
145 
146     /***
147      * Executes a HTTP get request and returns the response body.
148      *
149      * @param pathInfo path of database and optional e.g. a view name
150      * @param query the query string
151      * @return response body
152      * @throws IOException if the request cannot be executed
153      */
154     protected final String execute(final String pathInfo, final String query) throws IOException {
155         return executeUrl(pathInfo + "?" + query);
156     }
157 
158     /***
159      * Executes a HTTP get request and returns the response body.
160      *
161      * @param query the query string
162      * @return response body
163      * @throws IOException if the request cannot be executed
164      *
165      * @deprecated everything should be rewritten without the domingo database
166      */
167     protected final String execute(final String query) throws IOException {
168         return executeUrl(getDomingoDatabase() + "/Domingo?OpenAgent&" + query);
169     }
170 
171     /***
172      * Returns information from the domingo database on the server.
173      *
174      * @param infoname name of the information resource
175      * @return content of the information resource
176      * @throws IOException if the request cannot be executed
177      *
178      * @deprecated everything should be rewritten without the domingo database
179      */
180     protected final String executeDomingoDatabaseUrl(final String infoname) throws IOException {
181         return executeUrl(getDomingoDatabase() + "/" + infoname);
182     }
183 
184     /***
185      * Executes a given URL and returns the answer from the server.
186      *
187      * @param pathInfo the path_info to execute
188      * @return array of bytes with result from server
189      * @throws IOException if the URL cannot be executed
190      *
191      * TODO move this to the session
192      */
193     protected final String executeUrl(final String pathInfo) throws IOException {
194         final DominoHttpMethod method = getDSession().createGetMethod(pathInfo);
195         try {
196             final int statusCode = getDSession().executeMethod(method);
197             if (statusCode != HttpStatus.SC_OK) {
198                 getMonitor().error("Http request failed: " + method.getStatusText());
199                 throw new IOException("Error " + method.getStatusCode() + ": " + method.getStatusText() + ": " + pathInfo);
200             }
201             final byte[] responseBody = method.getResponseBody();
202             Header contentType = method.getRequestHeader("Content-Type");
203             String value = contentType.getValue();
204             String charset = value.substring(value.lastIndexOf('='));
205             if (!Charset.availableCharsets().containsKey(charset)) {
206                 throw new IOException("unsupported charset: " + charset);
207             }
208             ByteArrayInputStream is = new ByteArrayInputStream(responseBody);
209             InputStreamReader isReader = new InputStreamReader(is, charset);
210             BufferedReader reader = new BufferedReader(isReader);
211             StringBuffer response = new StringBuffer(responseBody.length);
212             String line = null;
213             do {
214                 line = reader.readLine();
215                 if (line != null) {
216                     response.append(line);
217                     response.append(LINE_TERM);
218                 }
219             } while (line != null);
220             return response.toString();
221         } catch (IOException e) {
222             getMonitor().error(e.getLocalizedMessage(), e);
223             throw e;
224         } finally {
225             method.releaseConnection();
226         }
227     }
228 
229     /***
230      * Executes a HTTP get request and returns the response body.
231      *
232      * @param query the query string
233      * @param document the document
234      * @return response body
235      * @throws IOException if the request cannot be executed
236      *
237      * @deprecated everything should be rewritten without the domingo database
238      */
239     protected final byte[] postDXL(final String query, final DocumentHttp document)
240             throws IOException {
241         final String pathInfo = "/" + getDomingoDatabase() + "/Domingo?OpenAgent&" + query;
242         final DominoPostMethod method = getDSession().createPostMethod(pathInfo);
243         final Iterator iterator = document.getItems();
244         final StringBuffer buffer = new StringBuffer();
245         buffer.append("<?xml version='1.0'?>");
246         buffer.append("<!DOCTYPE document>");
247         buffer.append("<document xmlns='http://www.lotus.com/dxl' version='7.0' form='" + document.getItemValue("Form") + "'>");
248         buffer.append("<noteinfo noteid='" + document.getNoteID() + "' unid='" + document.getUniversalID() + "' sequence='2'>");
249         buffer.append("<created><datetime>" + formatDateTime(document.getCreated()) + "</datetime></created>");
250         buffer.append("<modified><datetime>" + formatDateTime(document.getLastModified()) + "</datetime></modified>");
251         // TODO check this:
252         buffer.append("<revised><datetime>" + formatDateTime(document.getLastModified()) + "</datetime></revised>");
253         // TODO check this:
254         buffer.append("<lastaccessed><datetime>" + formatDateTime(document.getLastAccessed()) + "</datetime></lastaccessed>");
255         // TODO check this:
256         buffer.append("<addedtofile><datetime>" + formatDateTime(document.getLastModified()) + "</datetime></addedtofile>");
257         buffer.append("</noteinfo>");
258         while (iterator.hasNext()) {
259             final ItemHttp item = (ItemHttp) iterator.next();
260             if (!ignoreItem(item.getName())) {
261                 buffer.append("<item name='" + item.getName() + "' names='" + item.isNames() + "' readers='" + item.isReaders()
262                         + "' authors='" + item.isAuthors() + "' protected='" + item.isProtected() + "'>");
263                 appendValuesListStartTag(buffer, item);
264                 Iterator iter = item.getValues().iterator();
265                 while (iter.hasNext()) {
266                     Object object = iter.next();
267                     appendValue(buffer, object);
268                 }
269                 appendValuesListEndTag(buffer, item);
270                 buffer.append("</item>");
271             }
272         }
273         buffer.append("</document>");
274         method.setRequestBody(buffer.toString());
275         try {
276             final int statusCode = getDSession().executeMethod(method);
277             if (statusCode != HttpStatus.SC_OK) {
278                 getMonitor().error("Http request failed: " + method.getStatusLine());
279             }
280             return method.getResponseBody();
281         } catch (IOException e) {
282             getMonitor().error(e.getLocalizedMessage(), e);
283             throw e;
284         } finally {
285             method.releaseConnection();
286         }
287     }
288 
289     private String getDomingoDatabase() {
290         return getFactory().getDomingoDatabase();
291     }
292 
293     private void appendValuesListStartTag(final StringBuffer buffer, final ItemHttp item) {
294         if (item.getValues() == null) {
295             return;
296         } else if (item.getValues().size() <= 1) {
297             return;
298         } else if (item.getValues().get(0) instanceof String) {
299             buffer.append("<textlist>");
300         } else if (item.getValues().get(0) instanceof Number) {
301             buffer.append("<numberlist>");
302         } else if (item.getValues().get(0) instanceof Calendar) {
303             buffer.append("<datedtimelist>");
304         }
305     }
306 
307     private void appendValuesListEndTag(final StringBuffer buffer, final ItemHttp item) {
308         if (item.getValues() == null) {
309             return;
310         } else if (item.getValues().size() <= 1) {
311             return;
312         } else if (item.getValues().get(0) instanceof String) {
313             buffer.append("</textlist>");
314         } else if (item.getValues().get(0) instanceof Number) {
315             buffer.append("</numberlist>");
316         } else if (item.getValues().get(0) instanceof Calendar) {
317             buffer.append("</datedtimelist>");
318         }
319     }
320 
321     private void appendValue(final StringBuffer buffer, final Object object) {
322         if (object == null) {
323             buffer.append("<text/>");
324         } else if ("".equals(object)) {
325             buffer.append("<text/>");
326         } else if (object instanceof String) {
327             buffer.append("<text>" + formatString((String) object) + "</text>");
328         } else if (object instanceof Number) {
329             buffer.append("<number>" + formatNumber((Number) object) + "</number>");
330         } else if (object instanceof Calendar) {
331             buffer.append("<datetime>" + formatDateTime((Calendar) object) + "</datetime>");
332         }
333     }
334 
335     private static String formatString(final String value) {
336         return value.replaceAll("\n", "<break/>");
337     }
338 
339     private String formatNumber(final Number value) {
340         return value.toString();
341     }
342 
343     private String formatDateTime(final Calendar value) {
344         if (value == null) {
345             return "";
346         }
347         return XML_DATE_FORMAT.format(value.getTime());
348     }
349 
350     private static final String[] IGNORE_ITEMS = {"HTTP_User_Agent", "HTTP_HOST", "HTTP_CONTENT_LENGTH",
351             "HTTP_CONTENT_TYPE", "HTTP_AUTHORIZATION", "HTTPS", "CONTENT_LENGTH", "CONTENT_TYPE", "PATH_INFO",
352             "CGI_PATH_INFO", "PATH_TRANSLATED", "QUERY_STRING", "Query_String_Decoded", "REMOTE_HOST", "REMOTE_ADDR",
353             "REMOTE_IDENT", "REQUEST_METHOD", "SERVER_NAME", "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE",
354             "SERVER_ADDR", "AUTH_TYPE", "REMOTE_USER", "GATEWAY_INTERFACE", "SCRIPT_NAME", "PATH_INFO_DECODED",
355             "REQUEST_CONTENT" };
356 
357     private boolean ignoreItem(final String name) {
358         for (int i = 0; i < IGNORE_ITEMS.length; i++) {
359             if (IGNORE_ITEMS[i].equals(name)) {
360                 return true;
361             }
362         }
363         return false;
364     }
365 
366     // //////////////////////////////////////////////
367     // interface java.lang.Object
368     // //////////////////////////////////////////////
369 
370     /***
371      * {@inheritDoc}
372      * @see java.lang.Object#toString()
373      */
374     public abstract String toString();
375 
376     /***
377      * Returns a string representation of the object. This method
378      * returns a string that "textually represents" this object.
379      * The result is a concise but informative representation that
380      * is easy for a person to read.
381      *
382      * <p>The <code>toStringIntern</code> method
383      * returns a string consisting of the name of the class of which the
384      * object is an instance, the at-sign character `<code>@</code>', and
385      * the unsigned hexadecimal representation of the hash code of the
386      * object. In other words, this method returns a string equal to the
387      * value of:
388      * <blockquote>
389      * <pre>
390      * object.getClass().getName() + '@' + Integer.toHexString(object.hashCode())
391      * </pre></blockquote>
392      *
393      * @param object the reference object to use.
394      * @return a string representation of the object.
395      * @see java.lang.Object#toString()
396      */
397     public static String toStringIntern(final Object object) {
398         return object.getClass().getName() + "@" + Integer.toHexString(object.hashCode());
399     }
400 
401     ////////////////////////////////////////////////
402     //    inner classes
403     ////////////////////////////////////////////////
404 
405     /***
406      * Base SAX parser for DXL.
407      * Handles all kinds of item values.
408      */
409     class BaseHandler extends DefaultHandler {
410 
411         private List fValues = new ArrayList();
412 
413         private StringBuffer fValueBuffer = new StringBuffer();
414 
415         /***
416          * Resets the parser to start a new parsing.
417          */
418         protected void reset() {
419             fValues = new ArrayList();
420             fValueBuffer = new StringBuffer();
421         }
422 
423         /***
424          * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
425          *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
426          */
427         public void startElement(final String namespaceURI, final String localName, final String qName,
428                 final Attributes atts) throws SAXException {
429             if ("textlist".equals(qName)) {
430                 return;
431             } else if ("numberlist".equals(qName)) {
432                 return;
433             } else if ("datetimelist".equals(qName)) {
434                 return;
435             } else if ("text".equals(qName)) {
436                 fValueBuffer = new StringBuffer();
437             } else if ("number".equals(qName)) {
438                 fValueBuffer = new StringBuffer();
439             } else if ("datetime".equals(qName)) {
440                 fValueBuffer = new StringBuffer();
441             } else if ("break".equals(qName)) {
442                 fValueBuffer.append("\n");
443 //            } else if ("datetimepair".equals(qName)) {
444 //                // n/a
445             } else {
446                 super.startElement(namespaceURI, localName, qName, atts);
447             }
448         }
449 
450         /***
451          * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
452          */
453         public void endElement(final String uri, final String localName, final String qName) throws SAXException {
454             if ("textlist".equals(qName)) {
455                 return;
456             } else if ("numberlist".equals(qName)) {
457                 return;
458             } else if ("datetimelist".equals(qName)) {
459                 return;
460             } else if ("text".equals(qName)) {
461                 fValues.add(fValueBuffer.toString());
462             } else if ("number".equals(qName)) {
463                 fValues.add(new Double(fValueBuffer.toString()));
464             } else if ("datetime".equals(qName)) {
465                 fValues.add(parseViewEntryDateTime(fValueBuffer.toString())); // TODO parse date
466 //            } else if ("datetimepair".equals(localName)) {
467 //                // n/a
468             } else {
469                 super.endElement(uri, localName, qName);
470             }
471         }
472 
473         /***
474          * @see org.xml.sax.ContentHandler#characters(char[], int, int)
475          */
476         public void characters(final char[] ch, final int start, final int length) throws SAXException {
477             fValueBuffer.append(ch, start, length);
478         }
479 
480         /***
481          * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
482          */
483         public void warning(final SAXParseException e) throws SAXException {
484             getMonitor().warn("Parser warning", e);
485         }
486 
487         /***
488          * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
489          */
490         public void error(final SAXParseException e) throws SAXException {
491             getMonitor().error("Parser error", e);
492         }
493 
494         /***
495          * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
496          */
497         public void fatalError(final SAXParseException e) throws SAXException {
498             getMonitor().fatalError("Fatal parser error", e);
499         }
500 
501         /***
502          * Returns the parsed values list.
503          *
504          * @return list of parsed values
505          */
506         public List getValues() {
507             return fValues;
508         }
509     }
510 
511     /***
512      * Parses a date in the format as used in view entries.
513      *
514      * <p>Format: <tt>yyyyMMddThhmmss,nn+zz</tt></p>
515      *
516      * <p>Example: <tt>20070119T155258,93+01</tt></p>
517      *
518      * @param date a date/time value from a view entry
519      * @return parsed calendar
520      */
521     public static Calendar parseViewEntryDateTime(final String date) {
522         if (date == null || "".equals(date)) {
523             return null;
524         }
525         final int year = Integer.parseInt(date.substring(0, 4));
526         final int month = Integer.parseInt(date.substring(4, 6));
527         final int day = Integer.parseInt(date.substring(6, 8));
528         final int hour;
529         final int minute;
530         final int second;
531         if (date.length() > VIEW_ENTRY_DATE_LENGTH + 1) {
532             // TODO improve parsing, avoid magic naumbers
533             hour = Integer.parseInt(date.substring(VIEW_ENTRY_DATE_LENGTH + 1, VIEW_ENTRY_DATE_LENGTH + 3));
534             minute = Integer.parseInt(date.substring(VIEW_ENTRY_DATE_LENGTH + 2 + 1, VIEW_ENTRY_DATE_LENGTH + 2 + 2 + 1));
535             second = Integer.parseInt(date.substring(VIEW_ENTRY_DATE_LENGTH + 2 + 3, VIEW_ENTRY_DATE_LENGTH + 2 + 2 + 3));
536         } else {
537             hour = 0;
538             minute = 0;
539             second = 0;
540         }
541         int millis = 0;
542         if (date.length() > VIEW_ENTRY_DATE_LENGTH + VIEW_ENTRY_TIME_LENGTH) {
543             millis = Integer.parseInt(date.substring(VIEW_ENTRY_DATE_LENGTH + VIEW_ENTRY_TIME_LENGTH, VIEW_ENTRY_DATE_LENGTH
544                     + VIEW_ENTRY_TIME_LENGTH + 2));
545         }
546         final Calendar calendar = new GregorianCalendar(year, month - 1, day, hour, minute, second);
547         calendar.set(Calendar.MILLISECOND, millis);
548         return calendar;
549 
550     }
551 }