1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
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
87
88
89
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
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
252 buffer.append("<revised><datetime>" + formatDateTime(document.getLastModified()) + "</datetime></revised>");
253
254 buffer.append("<lastaccessed><datetime>" + formatDateTime(document.getLastAccessed()) + "</datetime></lastaccessed>");
255
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
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
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
444
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()));
466
467
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
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 }