001: package com.ibm.webdav;
002:
003: /*
004: * (C) Copyright IBM Corp. 2000 All rights reserved.
005: *
006: * The program is provided "AS IS" without any warranty express or
007: * implied, including the warranty of non-infringement and the implied
008: * warranties of merchantibility and fitness for a particular purpose.
009: * IBM will not be liable for any damages suffered by you as a result
010: * of using the Program. In no event will IBM be liable for any
011: * special, indirect or consequential damages or lost profits even if
012: * IBM has been advised of the possibility of their occurrence. IBM
013: * will not be liable for any third party claims against you.
014: *
015: * Portions Copyright (C) Simulacra Media Ltd, 2004.
016: */
017: import org.w3c.dom.*;
018: import java.util.*;
019:
020: import java.text.ParseException;
021:
022: /** A PropertyResponse describes the properties returned as the
023: * result of a method send on a resource, e.g., getProperties(). A
024: * <code>MultiStatus</code> contains a collection of Response instances,
025: * one for each resource effected by the method sent.</p>
026: * <p>
027: * PropertyResponse also has convenience methods for all the DAV properties.
028: * The method names correspond to the DAV property names suitably
029: * modified to fit JavaBean conventions. If the property was not requested,
030: * these convenience methods will return null or some other suitable value
031: * to indicate the property value is unknown.</p>
032: * @author Jim Amsden <jamsden@us.ibm.com>
033: * @see com.ibm.webdav.MethodResponse
034: * @see com.ibm.webdav.MultiStatus
035: */
036: public class PropertyResponse extends Response implements
037: java.io.Serializable {
038:
039: // a Dictionary whose key is a PropertyName and whose value is a PropertyValue
040: private Hashtable pnproperties = new Hashtable();
041:
042: // a Dictionary matching a property name string into a PropertyName.
043: // All PropertyNames in this dictionary have entries in the
044: // pnproperites Dictionary.
045: //
046: // The property name string is the property's XML namespace concatenated with the
047: // element local tag name (the name without the prefix and :). This is consistent
048: // with the current WebDAV semantics for XML namespaces
049: private Hashtable strxproperties = new Hashtable();
050:
051: //Table of property definitions
052: private Hashtable propertydefs = new Hashtable();
053:
054: /** Construct a Response from an XML DAV:response element
055: *
056: * @param document the document that will contain the Response
057: * when output as XML.
058: * @param response the XML DAV:response element that is the source
059: * @exception com.ibm.webdav.ServerException thrown if the XML for the response is incorrect
060: * of this response
061: */
062: public PropertyResponse(Document document, Element response)
063: throws ServerException {
064: super (document);
065: try {
066: // get the identity of the resource this is a response on
067: Element href = (Element) response.getElementsByTagNameNS(
068: "DAV:", "href").item(0);
069: setResource(((Text) href.getFirstChild()).getData());
070:
071: // get the properties in the response, and their statuses
072: NodeList propstats = response.getElementsByTagNameNS(
073: "DAV:", "propstat");
074: Element propstat = null;
075: for (int i = 0; i < propstats.getLength(); i++) {
076: propstat = (Element) propstats.item(i);
077: // get the properties in the propstat. Note that there should be at most
078: // one prop in the propstat, and it contains the actual properties
079: Element prop = (Element) propstat
080: .getElementsByTagNameNS("DAV:", "prop").item(0);
081:
082: // this is the status for all properties in this propstat
083: Element status = (Element) propstat
084: .getElementsByTagNameNS("DAV:", "status").item(
085: 0);
086: String statusMessage = ((Text) status.getFirstChild())
087: .getData();
088: StringTokenizer statusFields = new StringTokenizer(
089: statusMessage, " ");
090: statusFields.nextToken(); // skip the HTTP version
091: int statusCode = Integer.parseInt(statusFields
092: .nextToken());
093: NodeList properties = prop.getChildNodes();
094: Node property = null;
095: for (int j = 0; j < properties.getLength(); j++) {
096: property = (Node) properties.item(j);
097: // skip ignorable TXText elements
098: if (property.getNodeType() == Node.ELEMENT_NODE) {
099: Element el = (Element) property;
100: //el.collectNamespaceAttributes();
101: PropertyName propname = new PropertyName(el);
102: addProperty(propname, (Element) property,
103: statusCode);
104: }
105: }
106: }
107: Element responseDescription = (Element) response
108: .getElementsByTagNameNS("DAV:",
109: "responsedescription").item(0);
110: if (responseDescription != null) {
111: setDescription(((Text) responseDescription
112: .getFirstChild()).getData());
113: }
114: } catch (Exception exc) {
115: exc.printStackTrace();
116: throw new ServerException(
117: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
118: "Invalid PropertyResponse");
119: }
120: }
121:
122: /** Construct an empty Response for some resource.
123: *
124: * @param url the URL of the resource this is a response for
125: *
126: */
127: public PropertyResponse(String url) {
128: super (url);
129: }
130:
131: /** Add a property and its status to the collection of properties generated
132: * as a result of sending a method to a resource.
133: *
134: * @param propertyName the name of the property to add.
135: * @param propertyElement the value of the property
136: * @param status its status
137: * @exception com.ibm.webdav.ServerException thrown if the property is already in this response
138: */
139: public void addProperty(PropertyName propertyName,
140: Element propertyElement, int status) throws ServerException {
141: if (pnproperties.contains(propertyName)) {
142: throw new ServerException(
143: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
144: "Duplicate property in a Response");
145: } else {
146: pnproperties.put(propertyName, new PropertyValue(
147: propertyElement, status));
148: strxproperties.put(propertyName.asExpandedString(),
149: propertyName);
150: }
151: }
152:
153: /** Translate this Response into an XML response element.
154: * @return a DAV:response XML element
155: */
156: public Element asXML() {
157:
158: Element response = document.createElementNS("DAV:",
159: "D:response");
160:
161: Element href = document.createElementNS("DAV:", "D:href");
162:
163: href.appendChild(document.createTextNode(getResource()));
164: response.appendChild(href);
165:
166: // group the properties having the same status
167: Hashtable byStatus = new Hashtable();
168: Enumeration propertyNames = getPropertyNamesPN();
169: while (propertyNames.hasMoreElements()) {
170: PropertyName propertyName = (PropertyName) propertyNames
171: .nextElement();
172: PropertyValue propertyValue = getProperty(propertyName);
173: Integer status = new Integer(propertyValue.status);
174:
175: // keep a Vector of property names having this status
176: Vector props = null;
177: if (byStatus.containsKey(status)) {
178: props = (Vector) byStatus.get(status);
179: } else {
180: props = new Vector();
181: byStatus.put(status, props);
182: }
183: props.addElement(propertyName);
184: }
185: // generate XML for the propstat
186: Enumeration statuses = byStatus.keys();
187:
188: // if there are no properties, construct a propstat with a prop with no elements and
189: // an SC_OK status,
190: if (!statuses.hasMoreElements()) {
191: Element propstat = document.createElementNS("DAV:",
192: "D:propstat");
193:
194: Element prop = document.createElementNS("DAV:", "D:prop");
195:
196: propstat.appendChild(prop);
197: Element statusElement = document.createElementNS("DAV:",
198: "D:status");
199:
200: String statusText = HTTPVersion + " " + WebDAVStatus.SC_OK
201: + " "
202: + WebDAVStatus.getStatusMessage(WebDAVStatus.SC_OK);
203: statusElement.appendChild(document
204: .createTextNode(statusText));
205: propstat.appendChild(statusElement);
206: response.appendChild(propstat);
207: }
208:
209: // create a propstat for all those properties having the same status.
210: while (statuses.hasMoreElements()) {
211: Integer status = (Integer) statuses.nextElement();
212: Enumeration propertiesHavingThisStatus = ((Vector) byStatus
213: .get(status)).elements();
214: Element propstat = document.createElementNS("DAV:",
215: "D:propstat");
216:
217: Element prop = document.createElementNS("DAV:", "D:prop");
218:
219: // put all the properties having this status in the current prop element
220: while (propertiesHavingThisStatus.hasMoreElements()) {
221: PropertyName propertyName = (PropertyName) propertiesHavingThisStatus
222: .nextElement();
223: PropertyValue propertyValue = getProperty(propertyName);
224: //Element property = document.createElement(pn2);
225:
226: try {
227: prop.appendChild(document.importNode(
228: propertyValue.value, true));
229: } catch (NullPointerException e) {
230: System.err.println("Null pointer for property - "
231: + propertyName);
232: throw e;
233: }
234:
235: }
236: propstat.appendChild(prop);
237: String statusText = HTTPVersion + " " + status + " "
238: + WebDAVStatus.getStatusMessage(status.intValue());
239: Element statusElement = document.createElementNS("DAV:",
240: "D:status");
241:
242: statusElement.appendChild(document
243: .createTextNode(statusText));
244: propstat.appendChild(statusElement);
245: response.appendChild(propstat);
246: }
247:
248: Enumeration defKeys = propertydefs.keys();
249:
250: if (defKeys.hasMoreElements()) {
251: Element propdefn = document.createElementNS("DAV:",
252: "D:propdefn");
253: response.appendChild(propdefn);
254:
255: while (defKeys.hasMoreElements()) {
256: Object key = defKeys.nextElement();
257: PropertyDefinition propdef = (PropertyDefinition) propertydefs
258: .get(key);
259:
260: propdefn.appendChild(document.importNode(propdef
261: .asXML(), true));
262: }
263:
264: }
265:
266: if (getDescription() != null) {
267: Element description = document.createElementNS("DAV:",
268: "D:responsedescription");
269:
270: description.appendChild(document
271: .createTextNode(getDescription()));
272: response.appendChild(description);
273: }
274: return response;
275: }
276:
277: /** Get the active locks in the property response if any.
278: *
279: * @return a Vector of ActiveLock objects containing information about locks
280: * on the resource. The Vector will be empty if the lockdiscovery property
281: * was not requested.
282: * @exception com.ibm.webdav.WebDAVException
283: */
284: public Vector getActiveLocks() throws WebDAVException {
285: Vector allLocks = new Vector();
286: PropertyValue prop = getProp("DAV:lockdiscovery");
287: if (prop != null) {
288: NodeList activeLocks = ((Element) prop.value)
289: .getElementsByTagNameNS("DAV:", "activelock");
290: Element activeLock = null;
291: for (int i = 0; i < activeLocks.getLength(); i++) {
292: activeLock = (Element) activeLocks.item(i);
293: allLocks.addElement(new ActiveLock(activeLock));
294: }
295: }
296: return allLocks;
297: }
298:
299: /** The author of this resource. That is, the principal id of the user agent that
300: * initially created the resource.
301: * @return the DAV:author property or null if the property was not requested.
302: */
303: public String getAuthor() {
304: String author = null;
305: PropertyValue prop = getProp("DAV:author");
306: if (prop != null) {
307: Text pcdata = (Text) prop.value.getFirstChild();
308: if (pcdata != null) {
309: author = pcdata.getData();
310: }
311: }
312: return author;
313: }
314:
315: /** The date the resource revision was checked in.
316: * @return the DAV:checkin-date property or null if the property was not requested
317: * or the resource is not versioned or checked in.
318: */
319: public Date getCheckinDate() {
320: Date date = null;
321: PropertyValue prop = getProp("DAV:checkin-date");
322: if (prop != null) {
323: Text pcdata = (Text) prop.value.getFirstChild();
324: if (pcdata != null) {
325: String dateString = pcdata.getData();
326: try {
327: date = (new SimpleISO8601DateFormat())
328: .parse(dateString);
329: } catch (ParseException exc) {
330: System.err
331: .println("Invalid date format for creationdate in "
332: + getResource());
333: }
334: }
335: }
336: return date;
337: }
338:
339: /** The comment associated this resource.
340: * @return the DAV:comment property or null if the property was not requested.
341: */
342: public String getComment() {
343: String comment = null;
344: PropertyValue prop = getProp("DAV:comment");
345: if (prop != null) {
346: Text pcdata = (Text) prop.value.getFirstChild();
347: if (pcdata != null) {
348: comment = pcdata.getData();
349: }
350: }
351: return comment;
352: }
353:
354: /** The language the content is written in.
355: * @return the value of the DAV:getcontentlanguage property of null if the property was not requested
356: */
357: public String getContentLanguage() {
358: String contentLanguage = null;
359: PropertyValue prop = getProp("DAV:getcontentlanguage");
360: if (prop != null) {
361: Text pcdata = (Text) prop.value.getFirstChild();
362: if (pcdata != null) {
363: contentLanguage = pcdata.getData();
364: }
365: }
366: return contentLanguage;
367: }
368:
369: /** The length of the content of the resource or -1 if content length is not applicable.
370: * @return the DAV:getcontentlength property or -1 if the property was not requested.
371: */
372: public int getContentLength() {
373: int length = -1;
374: PropertyValue prop = getProp("DAV:getcontentlength");
375: if (prop != null) {
376: Text pcdata = (Text) prop.value.getFirstChild();
377: if (pcdata != null) {
378: try {
379: length = Integer.parseInt(pcdata.getData());
380: } catch (NumberFormatException exc) {
381: System.err.println("Bad contentlength property in "
382: + getResource());
383: }
384: }
385: }
386: return length;
387: }
388:
389: /** The MIME content type of the resource.
390: * @return the DAV:getcontenttype property or null if the property was not requested.
391: */
392: public String getContentType() {
393: String contentType = null;
394: PropertyValue prop = getProp("DAV:getcontenttype");
395: if (prop != null) {
396: Text pcdata = (Text) prop.value.getFirstChild();
397: if (pcdata != null) {
398: contentType = pcdata.getData();
399: }
400: }
401: return contentType;
402: }
403:
404: /** The date the resource was created.
405: * @return the DAV:creationdate property or null if the property was not requested.
406: */
407: public Date getCreationDate() {
408: Date date = null;
409: PropertyValue prop = getProp("DAV:creationdate");
410: if (prop != null) {
411: Text pcdata = (Text) prop.value.getFirstChild();
412: if (pcdata != null) {
413: String dateString = pcdata.getData();
414: try {
415: date = (new SimpleISO8601DateFormat())
416: .parse(dateString);
417: } catch (ParseException exc) {
418: System.err
419: .println("Invalid date format for creationdate in "
420: + getResource());
421: }
422: }
423: }
424: return date;
425: }
426:
427: /** A name for this resource suitable for display by client applications.
428: * @return the DAV:displayname property or null if the property was not requested.
429: */
430: public String getDisplayName() {
431: String displayName = null;
432: PropertyValue prop = getProp("DAV:displayname");
433: if (prop != null) {
434: Text pcdata = (Text) prop.value.getFirstChild();
435: if (pcdata != null) {
436: displayName = pcdata.getData();
437: }
438: }
439: return displayName;
440: }
441:
442: /** The resource entity tag, useful for verifying the state of a cached resource.
443: * @return The DAV:getetag property or null if the property was not requested.
444: */
445: public String getETag() {
446: String eTag = null;
447: PropertyValue prop = getProp("DAV:getetag");
448: if (prop != null) {
449: Text pcdata = (Text) prop.value.getFirstChild();
450: if (pcdata != null) {
451: eTag = pcdata.getData();
452: }
453: }
454: return eTag;
455: }
456:
457: /** The date on which the resource was last modified.
458: * @return the DAV:getlastmodified property or null if the property was not requested.
459: */
460: public Date getLastModifiedDate() {
461: Date date = null;
462: PropertyValue prop = getProp("DAV:getlastmodified");
463: if (prop != null) {
464: Text pcdata = (Text) prop.value.getFirstChild();
465: if (pcdata != null) {
466: String dateString = pcdata.getData();
467: try {
468: date = (new SimpleRFC1123DateFormat())
469: .parse(dateString);
470: } catch (ParseException exc) {
471: System.err
472: .println("Invalid date format for getlastmodified in "
473: + getResource());
474: }
475: }
476: }
477: return date;
478: }
479:
480: /** Internal helper method that returns the value
481: * of a property given its name.
482: * <p>
483: * If an invalid property name string (see
484: * PropertyName(String) constructor for definition)
485: * is provided, this routine will throw a runtime
486: * exception. This can be convenient if you know
487: * the property name string is valid and dont want
488: * to clean up exceptions that will never be thrown.
489: * (Runtime exceptions don't need to be declared.)
490: * If you need to find the value of a property and
491: * have a property name that you can't be sure is
492: * valid, use getProperty instead of this method.
493: *
494: * @return a PropertyValue for a given property name... or
495: * null if that property was not found.
496: *
497: * @exception RuntimeException
498: */
499: private PropertyValue getProp(String pnstr) {
500:
501: PropertyName pn;
502: try {
503: pn = new PropertyName(pnstr);
504: } catch (InvalidPropertyNameException excc) {
505: throw new RuntimeException("internal error: bad property: "
506: + pnstr);
507: }
508: PropertyValue prop = getProperty(pn);
509: return prop;
510: }
511:
512: /** Get all the properties and their statuses in this Response.
513: *
514: * @return a Dictionary whose keys are PropertyNames, and whose values are
515: * PropertyValues.
516: */
517: public Dictionary getPropertiesByPropName() {
518: return pnproperties;
519: }
520:
521: public Dictionary getPropertyDefinitionsByPropName() {
522: return this .propertydefs;
523: }
524:
525: /** Get the value of a property contained in a PropertyResponse. The
526: * values were set when a method was invoked that returned a MultiStatus
527: * containing one or more PropertyResponses.
528: *
529: * @param name the name of the property to get,
530: * @return the property value for the named property
531: */
532: public PropertyValue getProperty(PropertyName name) {
533: return (PropertyValue) pnproperties.get(name);
534: }
535:
536: /** Get the names of the properties contained in a PropertyResponse. The
537: * values were set when a method was invoked that returned a MultiStatus
538: * containing one or more PropertyResponses. The values here are instances
539: * of the class PropertyName... not String.
540: */
541: public Enumeration getPropertyNamesPN() {
542: return pnproperties.keys();
543: }
544:
545: /** Get the resource type for the resource associated with this PropertyResponse.
546: * This is the tagName of the first child of the DAV:resourcetype property.
547: * @return The DAV:resourcetype DAV property or null if the property was not requested.
548: */
549: public String getResourceType() {
550: String resourceType = null;
551: PropertyValue prop = getProp("DAV:resourcetype");
552: if (prop != null) {
553: Element type = (Element) prop.value.getFirstChild();
554: if (type != null) {
555: resourceType = type.getTagName();
556: }
557: }
558: return resourceType;
559: }
560:
561: /** Check to see if this response does not contain an error.
562: *
563: * @return true if all property statuses are less than 300.
564: */
565: public boolean isOK() {
566: boolean isOk = true;
567: Enumeration propertyValues = getPropertiesByPropName()
568: .elements();
569: while (isOk && propertyValues.hasMoreElements()) {
570: PropertyValue propertyValue = (PropertyValue) propertyValues
571: .nextElement();
572: isOk = isOk && propertyValue.status < 300;
573: }
574: return isOk;
575: }
576:
577: /** See if this property response is on a collection. That is, see
578: * if it contains a resourcetype property having a collection element.
579: *
580: * @return true if this PropertyResponse is on a collection resource
581: */
582: public boolean isOnACollection() {
583: PropertyValue resourcetype = getProp("DAV:resourcetype");
584: boolean isACollection = false;
585: if (resourcetype != null) {
586: Element value = (Element) resourcetype.value;
587: isACollection = value.getElementsByTagNameNS("DAV:",
588: "collection").getLength() > 0;
589: }
590: return isACollection;
591: }
592:
593: /** Remove a property from the collection of properties generated
594: * as a result of sending a method to a resource.
595: *
596: * @param propertyName the property to remove.
597: * @exception com.ibm.webdav.ServerException
598: */
599: public void removeProperty(PropertyName propertyName)
600: throws ServerException {
601: if (pnproperties.contains(propertyName)) {
602: strxproperties.remove(propertyName.asExpandedString());
603: pnproperties.remove(propertyName);
604: }
605: }
606:
607: /** Set the value of a property.
608: *
609: * @param name the name of the property to set.
610: * @param value the property value for the named property
611: */
612: public void setProperty(PropertyName name, PropertyValue value) {
613: strxproperties.put(name.asExpandedString(), name);
614: pnproperties.put(name, value);
615: }
616:
617: /** Convert this Response to a PropertyResponse.
618: * This method is used to convert MethodResponses to PropertyResponses
619: * when an error occurred accessing the properties of some member.
620: *
621: */
622: public PropertyResponse toPropertyResponse() {
623: return this;
624: }
625: }
|