001: package com.ibm.webdav.impl;
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 java.io.*;
018: import java.util.*;
019:
020: import javax.xml.parsers.*;
021:
022: import org.w3c.dom.*;
023:
024: import com.ibm.webdav.*;
025:
026: /** CachedPropertiesManager implements the Properties interface by loading
027: * all properties on an initial request, and then accessing from the
028: * cache to satisfy more specific requests. This implementation is
029: * appropriate in those situations where reading all the properties
030: * is just as efficient as reading one. CachedProperties is abstract
031: * because it relies on specific subclasses to support the actual
032: * reading and writing of the properties.
033: * @author Jim Amsden <jamsden@us.ibm.com>
034: */
035: public abstract class CachedPropertiesManager implements
036: PropertiesManager {
037: private static final int set = 0;
038: private static final int remove = 1;
039: protected ResourceImpl resource = null;
040:
041: /** The default constructor.
042: */
043: public CachedPropertiesManager() {
044: }
045:
046: /** Create a properties manager for the given resource and its
047: * namespace manager.
048: * @param resource the resource whose properties are to be managed
049: * @param namespaceManager its namespace manager
050: */
051: public CachedPropertiesManager(ResourceImpl resource,
052: com.ibm.webdav.impl.NamespaceManager namespaceManager) {
053: initialize(resource, namespaceManager);
054: }
055:
056: /** Delete all properties for the managed resource
057: * @exception IOException
058: * @exception com.ibm.webdav.WebDAVException
059: */
060: public abstract void deleteProperties() throws WebDAVException;
061:
062: /** Get all the properties of this resource.
063: * This implementation stores properties in an XML document containing a properties
064: * root element. The properties file name is derived from the URI by adding the extension
065: * PropertiesManager.propertiesSuffix. This applies to collections as well as other resources.
066: * <p>
067: * Since the properties are stored in a file, and all methods that query and
068: * update the properties must read the XML file into memory and parse it,
069: * many of the other property methods are implemented by calling this method.
070: * Subclasses of ResourceImpl may want to use other techniques depending on
071: * how the properties are stored.
072: * </p>
073: * @return a MultiStatus containing response elements with prop elements
074: * containing the properties and their statuses.
075: * @exception com.ibm.webdav.WebDAVException
076: */
077: public MultiStatus getProperties() throws WebDAVException {
078: Document propertiesDocument = resource.loadProperties();
079:
080: // create a MultiStatus to hold the results
081: MultiStatus results = new MultiStatus();
082:
083: // create a response element to hold the properties for this resource
084: PropertyResponse response = null;
085: String urlstring;
086:
087: if (false) {
088: // I consider this to be the more correct way and the
089: // way used in the examples in the spec... but it is
090: // redundant and creates the possibility of the two
091: // redundant parts being out of synch.
092: urlstring = resource.getURL().toString();
093: } else {
094: // this is the way that mod_dav and a few others do it. This
095: // way also makes it easier to debug clients even if
096: // redirecting through a dedicated proxy. Without this
097: // it's inconvenient to debug IE5. It gets confused if
098: // the host:port (if provided) don't match who it thinks
099: // it's connecting to.
100: urlstring = resource.getURL().getFile();
101: }
102:
103: response = new PropertyResponse(urlstring);
104:
105: // add the properties to the response
106: NodeList properties = propertiesDocument.getDocumentElement()
107: .getChildNodes();
108: Node temp = null;
109:
110: for (int i = 0; i < properties.getLength(); i++) {
111: temp = properties.item(i);
112:
113: // Skip ignorable TXText elements
114: if (!(temp.getNodeType() == Node.ELEMENT_NODE)) {
115: continue;
116: }
117:
118: Element property = (Element) temp;
119: PropertyName pn = new PropertyName(property);
120: response.addProperty(pn, property, WebDAVStatus.SC_OK);
121: }
122:
123: results.addResponse(response);
124:
125: return results;
126: }
127:
128: /** Get the named properties for this resource and (potentially) its children.
129: *
130: * @param names an arrary of property names to retrieve
131: * @return a MultiStatus of PropertyResponses
132: * @exception com.ibm.webdav.WebDAVException
133: */
134: public MultiStatus getProperties(PropertyName[] names)
135: throws WebDAVException {
136: MultiStatus multiStatus = resource.getProperties(resource
137: .getContext());
138: MultiStatus newMultiStatus = new MultiStatus();
139:
140: Enumeration responses = multiStatus.getResponses();
141:
142: while (responses.hasMoreElements()) {
143: PropertyResponse response = (PropertyResponse) responses
144: .nextElement();
145: PropertyResponse newResponse = new PropertyResponse(
146: response.getResource());
147: newResponse.setDescription(response.getDescription());
148: newMultiStatus.addResponse(newResponse);
149:
150: Hashtable properties = (Hashtable) response
151: .getPropertiesByPropName();
152:
153: //Hashtable newProperties = (Hashtable) newResponse.getProperties();
154: for (int i = 0; i < names.length; i++) {
155: if (properties.containsKey(names[i])) {
156: PropertyValue srcval = response
157: .getProperty(names[i]);
158: newResponse.setProperty(names[i], srcval);
159:
160: //newProperties.put(names[i], properties.get(names[i]));
161: } else {
162: Document factory = null;
163:
164: try {
165: factory = DocumentBuilderFactory.newInstance()
166: .newDocumentBuilder().newDocument();
167: } catch (Exception e) {
168: throw new WebDAVException(
169: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
170: e.getMessage());
171: }
172:
173: // we'll create an xml element with no value because that's
174: // what webdav will need to return for most methods... even if the
175: // property doesn't exist. That's because the
176: // distinction between a propertyname and propertyvalue
177: // is fuzzy in WebDAV xml. A property name is
178: // essentially an empty property value because
179: // all property values have their property
180: // name stuck on.
181: // if we decide to set reviewStatus to null instead (as
182: // we did previously, then the code for MultiStatus.asXML()
183: // needs to be updated to expect null values.
184: // (jlc 990520)
185: Element elTm = factory.createElementNS("X", "X:"
186: + names[i].getLocal());
187:
188: elTm.setAttribute("xmlns:X", names[i]
189: .getNamespace());
190:
191: newResponse.addProperty(names[i], elTm,
192: WebDAVStatus.SC_NOT_FOUND);
193: }
194: }
195: }
196:
197: return newMultiStatus;
198: }
199:
200: /** Get the names of all properties for this resource. This implementation
201: * reads all the properties and then extracts their names.
202: *
203: * @return a MultiStatus of PropertyResponses
204: * (PropertyValue.value is always null, PropertyValue.status contains the status)
205: * @exception com.ibm.webdav.WebDAVException
206: */
207: public MultiStatus getPropertyNames() throws WebDAVException {
208: MultiStatus multiStatus = resource.getProperties(resource
209: .getContext());
210: Enumeration responses = multiStatus.getResponses();
211:
212: // we have the result, but all of the properties in our structure contain
213: // values. We don't want to include values. Just names. The following
214: // code strips out the content of these elements.
215: while (responses.hasMoreElements()) {
216: PropertyResponse response = (PropertyResponse) responses
217: .nextElement();
218: Dictionary properties = response.getPropertiesByPropName();
219: Enumeration keys = properties.keys();
220:
221: while (keys.hasMoreElements()) {
222: PropertyName key = (PropertyName) keys.nextElement();
223: Element value = (Element) response.getProperty(key)
224: .getValue();
225: response.setProperty(key, new PropertyValue(
226: (Element) value.cloneNode(false),
227: WebDAVStatus.SC_OK));
228: }
229: }
230:
231: return multiStatus;
232: }
233:
234: /** Initialize this properties manager.
235: * @param resource the resource whose properties are to be managed
236: * @param namespaceManager its namespace manager
237: */
238: public void initialize(ResourceImpl resource,
239: com.ibm.webdav.impl.NamespaceManager namespaceManager) {
240: this .resource = resource;
241: }
242:
243: /** Is a property live, i.e., has semantics supported by the server?
244: * @param propertyName the name of the property to check
245: * @return true if the named property is live on this server
246: */
247: public boolean isLive(String propertyName) {
248: return propertyName.equals("creationdate")
249: || propertyName.equals("getcontentlength")
250: || propertyName.equals("getlastmodified")
251: || propertyName.equals("lockdiscovery")
252: || propertyName.equals("getcontenttype")
253: || propertyName.equals("resourcetype")
254: || propertyName.equals("supportedlock")
255: || propertyName.equals("DAV:creationdate")
256: || propertyName.equals("DAV:getcontentlength")
257: || propertyName.equals("DAV:getlastmodified")
258: || propertyName.equals("DAV:lockdiscovery")
259: || propertyName.equals("DAV:getcontenttype")
260: || propertyName.equals("DAV:resourcetype")
261: || propertyName.equals("DAV:supportedlock");
262: }
263:
264: /** Load properties from their persistent store.
265: * @return an XML document containing a properties element.
266: * @exception com.ibm.webdav.WebDAVException
267: */
268: public abstract Document loadProperties() throws WebDAVException;
269:
270: /** Remove the live DAV properties from the properties document that
271: * do not need to be saved. There is no reason to save them as long
272: * as they are recalculated each time the properties are loaded. This
273: * method removes the ones that are repository specific.
274: * @param propertiesDocument the XML document containing the properties. Elements
275: * are removed from this document that don't need to be saved.
276: */
277: public abstract void removeLiveProperties(
278: Document propertiesDocument);
279:
280: /** Save the properties to the persistent store.
281: * @param propertiesDocument an XML document containing a properties element.
282: * @exception com.ibm.webdav.WebDAVException
283: */
284: public abstract void saveProperties(Document propertiesDocument)
285: throws WebDAVException;
286:
287: /** Edit the properties of a resource. The updates must refer to a Document containing a WebDAV
288: * propertyupdates element as the document root.
289: *
290: * @param updates an XML Document containing propertyupdate elements
291: * @return the result of making the updates
292: * describing the edits to be made.
293: * @exception com.ibm.webdav.WebDAVException
294: */
295: public MultiStatus setProperties(Document propertyUpdates)
296: throws WebDAVException {
297: // create a MultiStatus to hold the results. It will hold a MethodResponse
298: // for each update, and one for the method as a whole
299: MultiStatus multiStatus = new MultiStatus();
300: boolean errorsOccurred = false;
301:
302: // first, load the properties so they can be edited
303: Document propertiesDocument = resource.loadProperties();
304: Element properties = (Element) propertiesDocument
305: .getDocumentElement();
306:
307: // be sure the updates have at least one update
308: Element propertyupdate = (Element) propertyUpdates
309: .getDocumentElement();
310: String tagName = propertyupdate.getNamespaceURI()
311: + propertyupdate.getLocalName();
312:
313: if (!tagName.equals("DAV:propertyupdate")) {
314: throw new WebDAVException(
315: WebDAVStatus.SC_UNPROCESSABLE_ENTITY,
316: "missing propertyupdate element");
317: }
318:
319: NodeList updates = propertyupdate.getChildNodes();
320:
321: if (updates.getLength() == 0) {
322: throw new WebDAVException(
323: WebDAVStatus.SC_UNPROCESSABLE_ENTITY,
324: "no updates in request");
325: }
326:
327: Vector propsGood = new Vector(); // a list of properties that
328:
329: // were patched correctly or would have been if another
330: // property hadn't gone bad.
331: // apply the updates
332: Node temp = null;
333:
334: for (int i = 0; i < updates.getLength(); i++) {
335: temp = updates.item(i);
336:
337: // skip any ignorable TXText elements
338: if (!(temp.getNodeType() == Node.ELEMENT_NODE)) {
339: continue;
340: }
341:
342: Element update = (Element) temp;
343: int updateCommand = -1;
344: tagName = update.getNamespaceURI() + update.getLocalName();
345:
346: if (tagName.equals("DAV:set")) {
347: updateCommand = set;
348: } else if (tagName.equals("DAV:remove")) {
349: updateCommand = remove;
350: } else {
351: throw new WebDAVException(
352: WebDAVStatus.SC_UNPROCESSABLE_ENTITY,
353: update.getTagName()
354: + " is not a valid property update request");
355: }
356:
357: // iterate through the props in the set or remove element and update the
358: // properties as directed
359: Element prop = (Element) update.getElementsByTagNameNS(
360: "DAV:", "prop").item(0);
361:
362: if (prop == null) {
363: throw new WebDAVException(
364: WebDAVStatus.SC_UNPROCESSABLE_ENTITY,
365: "no propeprties in update request");
366: }
367:
368: NodeList propsToUpdate = prop.getChildNodes();
369:
370: for (int j = 0; j < propsToUpdate.getLength(); j++) {
371: temp = propsToUpdate.item(j);
372:
373: // skip any TXText elements??
374: if (!(temp.getNodeType() == Node.ELEMENT_NODE)) {
375: continue;
376: }
377:
378: Element propToUpdate = (Element) temp;
379:
380: // find the property in the properties element
381: Element property = null;
382: PropertyName propertyName = new PropertyName(
383: propToUpdate);
384:
385: if (((Element) propToUpdate).getNamespaceURI() != null) {
386: property = (Element) properties
387: .getElementsByTagNameNS(
388: propToUpdate.getNamespaceURI(),
389: propToUpdate.getLocalName())
390: .item(0);
391: } else {
392: property = (Element) properties
393: .getElementsByTagName(
394: propToUpdate.getTagName()).item(0);
395: }
396:
397: boolean liveone = isLive(propertyName
398: .asExpandedString());
399:
400: if (liveone) {
401: errorsOccurred = true;
402:
403: PropertyResponse response = new PropertyResponse(
404: resource.getURL().toString());
405: response.addProperty(propertyName, propToUpdate,
406: WebDAVStatus.SC_FORBIDDEN);
407: multiStatus.addResponse(response);
408: }
409:
410: // do the update
411: if (updateCommand == set) {
412: if (property != null) {
413: try {
414: properties.removeChild(property);
415: } catch (DOMException exc) {
416: }
417: }
418:
419: if (!liveone) {
420: // I don't think we're allowed to update live properties
421: // here. Doing so effects the cache. A case in
422: // point is the lockdiscoveryproperty. properties
423: // is actually the properites cache "document" of this
424: // resource. Even though we don't "save" the request
425: // if it includes live properties, we don't remove
426: // it from the cache after we'd set it here, so it
427: // can affect other queries. (jlc 991002)
428: properties.appendChild(propertiesDocument
429: .importNode(propToUpdate, true));
430:
431: propsGood.addElement(propToUpdate);
432: }
433: } else if (updateCommand == remove) {
434: try {
435: if (property != null) {
436: properties.removeChild(property);
437: propsGood.addElement(propToUpdate);
438: }
439: } catch (DOMException exc) {
440: }
441: }
442: }
443: }
444:
445: {
446: Enumeration els = propsGood.elements();
447:
448: for (; els.hasMoreElements();) {
449: Object ob1 = els.nextElement();
450: Element elProp = (Element) ob1;
451: PropertyName pn = new PropertyName(elProp);
452: PropertyResponse response = new PropertyResponse(
453: resource.getURL().toString());
454: response
455: .addProperty(
456: pn,
457: (Element) elProp.cloneNode(false),
458: (errorsOccurred ? WebDAVStatus.SC_FAILED_DEPENDENCY
459: : WebDAVStatus.SC_OK));
460:
461: // todo: add code for responsedescription
462: multiStatus.addResponse(response);
463: }
464: }
465:
466: // write out the properties
467: if (!errorsOccurred) {
468: resource.saveProperties(propertiesDocument);
469: }
470:
471: return multiStatus;
472: }
473:
474: /** Set a property of a resource to a value.
475: *
476: * @param name the property name
477: * @param value the property value
478: * @exception com.ibm.webdav.WebDAVException
479: */
480: public void setProperty(String name, Element value)
481: throws WebDAVException {
482: // load the properties
483: Document propertiesDocument = resource.loadProperties();
484: Element properties = propertiesDocument.getDocumentElement();
485: String ns = value.getNamespaceURI();
486:
487: Element property = null;
488: if (ns == null) {
489: property = (Element) ((Element) properties)
490: .getElementsByTagName(value.getTagName()).item(0);
491: } else {
492: property = (Element) properties.getElementsByTagNameNS(ns,
493: value.getLocalName()).item(0);
494: }
495:
496: if (property != null) {
497: try {
498: properties.removeChild(property);
499: } catch (DOMException exc) {
500: }
501: }
502:
503: properties.appendChild(propertiesDocument.importNode(value,
504: true));
505:
506: // write out the properties
507: resource.saveProperties(propertiesDocument);
508: }
509:
510: /** Update the live properties that are unique to the
511: * repository implementation.
512: * @param document the XML document containing the properties
513: * @exception com.ibm.webdav.WebDAVException
514: */
515: public abstract void updateLiveProperties(Document document)
516: throws WebDAVException;
517: }
|