001: /*
002: * Copyright (c) 2000, Jacob Smullyan.
003: *
004: * This is part of SkunkDAV, a WebDAV client. See http://skunkdav.sourceforge.net/
005: * for the latest version.
006: *
007: * SkunkDAV is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License as published
009: * by the Free Software Foundation; either version 2, or (at your option)
010: * any later version.
011: *
012: * SkunkDAV is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with SkunkDAV; see the file COPYING. If not, write to the Free
019: * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
020: * 02111-1307, USA.
021: */
022:
023: package org.skunk.dav.client;
024:
025: import java.io.IOException;
026: import java.util.*;
027: import org.skunk.minixml.*;
028: import org.skunk.trace.Debug;
029:
030: /**
031: * base class for DAV methods.
032: */
033: public abstract class AbstractDAVMethod implements DAVMethod {
034: private Map reqHeaders = new HashMap();
035: private Map resHeaders = new HashMap();
036: private byte[] reqBody, resBody;
037: private String path;
038: private int status;
039: private DAVFile daFile;
040:
041: /**
042: * @param path the resource on which the method is performed
043: */
044: public AbstractDAVMethod(String path) {
045: this .path = path;
046: daFile = new DAVFile(path);
047: }
048:
049: /**
050: * sets the protocol
051: * @param protocol the protocol
052: */
053: public void setProtocol(String protocol) {
054: daFile.setProtocol(protocol);
055: }
056:
057: /**
058: * returns the protocol
059: * @return the protocol
060: */
061: public String getProtocol() {
062: return daFile.getProtocol();
063: }
064:
065: /**
066: * sets the hostname
067: * @param host the hostname
068: */
069: public void setHost(String host) {
070: daFile.setHost(host);
071: }
072:
073: /**
074: * sets the port
075: * @param port the port
076: */
077: public void setPort(int port) {
078: daFile.setPort(port);
079: }
080:
081: /**
082: * gets the hostname of the server.
083: * @return the hostname
084: */
085: public String getHost() {
086: return daFile.getHost();
087: }
088:
089: /**
090: * gets the port where the file in question is served.
091: * @return the port
092: */
093: public int getPort() {
094: return daFile.getPort();
095: }
096:
097: /**
098: * gets the headers of the request
099: * @return the request headers
100: */
101: public Map getRequestHeaders() {
102: return reqHeaders;
103: }
104:
105: /**
106: * sets the headers of the request
107: * @param m a map of header names and values
108: */
109: public void setRequestHeaders(Map m) {
110: reqHeaders = m;
111: }
112:
113: /**
114: * sets the body of the request.
115: * @param b the new request body
116: */
117: public void setRequestBody(byte[] b) {
118: reqBody = b;
119: }
120:
121: /**
122: * returns the path requested
123: * @return the path requested.
124: */
125: public String getRequestURL() {
126: return path;
127: }
128:
129: /**
130: * returns the body of the request
131: * @return the body of the request, if any, or null
132: */
133: public byte[] getRequestBody() {
134: return reqBody;
135: }
136:
137: /**
138: * used to populate the headers of the response
139: * @param headers a mapping of headers names to values
140: */
141: public void setResponseHeaders(Map headers) {
142: this .resHeaders = headers;
143: }
144:
145: /**
146: * used to populate the body of the response
147: *
148: */
149: public void setResponseBody(byte[] body) {
150: this .resBody = body;
151: }
152:
153: /**
154: * @return the headers of the response
155: */
156: public Map getResponseHeaders() {
157: return resHeaders;
158: }
159:
160: /**
161: * @return the body of the response
162: */
163: public byte[] getResponseBody() {
164: return resBody;
165: }
166:
167: /**
168: * @return the status of the response
169: */
170: public int getStatus() {
171: return status;
172: }
173:
174: /**
175: * used to the populate the status of the response
176: * @param status the status
177: */
178: public void setStatus(int status) {
179: this .status = status;
180: }
181:
182: /**
183: * @return the DAVFile corresponding to the resourceURL.
184: */
185: public DAVFile getDAVFile() {
186: return daFile;
187: }
188:
189: /**
190: * a hook into processing the body of the response. By default will
191: * parse a multistatus response; otherwise, does nothing.
192: */
193: public void processResponseBody() throws MalformedXMLException {
194: if (getStatus() == 207)
195: parseMultistatus(getResponseBody());
196: }
197:
198: /**
199: * a hook for processing the response headers. By default does nothing.
200: */
201: public void processResponseHeaders() {
202: //by default, do nothing
203: }
204:
205: /**
206: * a hook for processing the request headers. By default does nothing.
207: */
208: public void processRequestHeaders() {
209: //by default, do nothing
210: }
211:
212: /**
213: * a hook for processing the request body. By default does nothing.
214: */
215: public void processRequestBody() {
216: //by default, do nothing
217: }
218:
219: private void parseMultistatus(byte[] responseBody)
220: throws MalformedXMLException {
221: XMLParser parser = new XMLParser();
222: try {
223: XMLDocument doc = parser.parse(new String(responseBody));
224: //for debugging only!
225: if (Debug.isDebug(this , Debug.DP8)) {
226: XMLViewer.viewDocumentDialog(doc);
227: }
228: //populate the DAVFile
229: populateFile(doc);
230: } catch (IOException oyVeh) {
231: Debug.trace(this , Debug.DP1, oyVeh);
232: }
233: }
234:
235: private void populateFile(XMLDocument doc) {
236: //there is no guarantee of order in the response listing of the multistatus
237: XMLElement multistatus = doc.getRootElement();
238: if (multistatus == null
239: || (!multistatus.getElementName().equals(
240: DAVConstants.MULTISTATUS_ELEM))) {
241: Debug.trace(this , Debug.DP2,
242: "multistatus element not found in response body");
243: return;
244: }
245: for (ListIterator lit = multistatus.children(); lit.hasNext();) {
246: //we should have response elements here
247: Object o = lit.next();
248: if (!(o instanceof XMLElement)) {
249: Debug.trace(this , Debug.DP3,
250: "weird object inside multistatus response: "
251: + o);
252: continue;
253: }
254: XMLElement elem = (XMLElement) o;
255: if (elem.getElementName().equals(
256: DAVConstants.RESPONSEDESCRIPTION_ELEM)) {
257: Object desc = elem.getChild(0);
258: if (desc != null)
259: daFile.setResponseDescription(desc.toString());
260: } else if (elem.getElementName().equals(
261: DAVConstants.RESPONSE_ELEM)) {
262: XMLElement href = elem.getChild(DAVConstants.HREF_ELEM);
263:
264: if (href == null || ((o = href.getChild(0)) == null)) {
265: Debug
266: .trace(this , Debug.DP2,
267: "multistatus response object in illegal format: no href");
268: continue;
269: }
270:
271: String filename = o.toString().trim();
272: //if the resource is a collection, its name may be returned ending with a slash.
273: if (stripLastSlash(daFile.getName()).equals(
274: stripLastSlash(filename))) {
275: populate(daFile, elem);
276: } else {
277: //file should be a collection: add new DAVFile as child
278: DAVFile child = new DAVFile(daFile.getProtocol(),
279: daFile.getHost(), daFile.getPort(),
280: filename);
281: populate(child, elem);
282: daFile.addChild(child);
283: }
284:
285: }
286: }
287: }
288:
289: /**
290: * chomps the last slash off a path, which may not be null.
291: * @param path the path to chomp
292: */
293: protected static final String stripLastSlash(String path) {
294: if (path.endsWith("/")) {
295: return path.substring(0, path.length() - 1);
296: } else
297: return path;
298: }
299:
300: private void populate(DAVFile file, XMLElement responseElem) {
301: /*response has three possible child nodes: href, status and propstat.
302: href has already been read, in order to create the DAVFile.
303: status is what it sounds like.
304: propstat has two child nodes: prop and status.
305: prop may return a number of properties, depending on the propfind
306: query type.
307: */
308: XMLElement statusElem = responseElem
309: .getChild(DAVConstants.STATUS_ELEM);
310: if (statusElem != null) {
311: Object o = statusElem.getChild(0);
312: if (o == null) {
313: Debug.trace(this , Debug.DP2,
314: "null status elem found for " + file.getName());
315: return;
316: }
317: file.setStatus(extractStatus(o.toString()));
318: }
319: //By adding the namespace specification, achieves Zope interoperability, which sends two propstats
320: //with different namespaces! Oy veh. -- js Sat Sep 1 12:59:02 2001
321: XMLElement propstatElem = responseElem.getChild(
322: DAVConstants.PROPSTAT_ELEM, DAVConstants.DAV_NAMESPACE);
323: if (propstatElem == null) {
324: Debug.trace(this , Debug.DP2,
325: "no propstat element found in response for "
326: + file.getName());
327: return;
328: }
329:
330: /********first, the status element ************/
331:
332: XMLElement elem = propstatElem
333: .getChild(DAVConstants.STATUS_ELEM);
334: if (elem == null) {
335: Debug.trace(this , Debug.DP2,
336: "no status element found in propstat for "
337: + file.getName());
338: return;
339: }
340: Object o = elem.getChild(0);
341: if (o == null) {
342: Debug.trace(this , Debug.DP2,
343: "no status string found in status element for "
344: + file.getName());
345: return;
346: }
347: file.setStatus(extractStatus(o.toString()));
348: /********then, the responsedescription, if any *************/
349: XMLElement responsedescriptionElem = propstatElem
350: .getChild(DAVConstants.RESPONSEDESCRIPTION_ELEM);
351: if (responsedescriptionElem != null) {
352: Object desc = responsedescriptionElem.getChild(0);
353: if (desc != null) {
354: //file may have a responsedescription set a the top level, too.
355: Debug.trace(this , Debug.DP3, "responseDescription: "
356: + desc);
357: String currentDescription = file
358: .getResponseDescription();
359: Debug.trace(this , Debug.DP3, "current description: "
360: + currentDescription);
361: StringBuffer sb = new StringBuffer();
362: if (currentDescription != null)
363: sb.append(currentDescription).append(": ");
364: sb.append(desc);
365: file.setResponseDescription(sb.toString());
366: }
367: }
368:
369: /********now, the props ************/
370:
371: elem = propstatElem.getChild(DAVConstants.PROP_ELEM);
372: processPropElement(file, elem);
373:
374: }
375:
376: /**
377: * by default, passes the properties off to PropertyHandler
378: * @param file the DAVFile in which the results are stored
379: * @param elem the prop XMLElement being processed
380: */
381: protected void processPropElement(DAVFile file, XMLElement elem) {
382: for (ListIterator lit = elem.children(); lit.hasNext();) {
383: Object aProperty = lit.next();
384: if (!(aProperty instanceof XMLElement))
385: continue;
386: XMLElement propElem = (XMLElement) aProperty;
387: PropertyHandler.handleProperty(file, propElem);
388: }
389: }
390:
391: /**
392: * convenience function for parsing the status header.
393: * @param statusLine the HTTP status line
394: * @return the status as an Integer
395: */
396: protected static final Integer extractStatus(String statusLine) {
397: StringTokenizer st = new StringTokenizer(statusLine);
398: Integer status = null;
399: if (st.countTokens() >= 2) {
400: st.nextToken(); //discard "HTTP/1.1"
401: String statusStr = st.nextToken();
402: try {
403: status = Integer.decode(statusStr);
404: } catch (NumberFormatException nafta) {
405: Debug.trace(AbstractDAVMethod.class, Debug.DP3, nafta);
406: }
407: }
408: return status;
409: }
410: }
411:
412: /* $Log: AbstractDAVMethod.java,v $
413: /* Revision 1.16 2001/09/01 18:25:56 smulloni
414: /* fixes for Zope interoperability; version 1.0.2.4 dev
415: /*
416: /* Revision 1.15 2001/07/20 18:45:21 smulloni
417: /* fixed handling of <response/> elements, which was skipping status elements
418: /* not contained in a propstat.
419: /*
420: /* Revision 1.14 2001/01/03 22:51:53 smulloni
421: /* added documentation
422: /*
423: /* Revision 1.13 2001/01/03 20:11:30 smulloni
424: /* the DAVFileChooser now replaces JFileChooser for remote file access.
425: /* DAVMethod now has a protocol property.
426: /*
427: /* Revision 1.12 2001/01/02 17:43:38 smulloni
428: /* changed debug level for XMLViewer popups to Debug.DP8.
429: /*
430: /* Revision 1.11 2000/12/19 22:06:15 smulloni
431: /* adding documentation.
432: /*
433: /* Revision 1.10 2000/12/03 23:53:25 smulloni
434: /* added license and copyright preamble to java files.
435: /*
436: /* Revision 1.9 2000/11/14 23:18:31 smullyan
437: /* fix for responsedescription property in multistatus
438: /*
439: /* Revision 1.8 2000/11/09 23:34:47 smullyan
440: /* log added to every Java file, with the help of python. Lock stealing
441: /* implemented, and treatment of locks made more robust.
442: /* */
|