- Moved the dtstart,dtend fixing to icalUtilities so it can be called from updateFrom...
[jgroupdav.git] / src / main / java / net / bionicmessage / groupdav / CalDAVExtensions.java
blob458d3ece2dae53b4f5806dfd5c4d3291f89fa207
1 package net.bionicmessage.groupdav;
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.io.UnsupportedEncodingException;
6 import java.net.URL;
8 /*
9 * bionicmessage jgroupdav library
10 * CalDAVExtensions.java created Friday May 23rd 2008, (C) 2008 Mathew McBride
12 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the "Software"),
14 * to deal in the Software without restriction, including without limitation
15 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 * and/or sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 * IN THE SOFTWARE.
31 /**
32 * Implements some CalDAV queries to supplement GroupDAV
33 * @author matt
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.Hashtable;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Stack;
42 import java.util.logging.Logger;
43 import javax.xml.namespace.NamespaceContext;
44 import javax.xml.parsers.DocumentBuilder;
45 import javax.xml.parsers.DocumentBuilderFactory;
46 import javax.xml.xpath.XPath;
47 import javax.xml.xpath.XPathConstants;
48 import javax.xml.xpath.XPathExpression;
49 import javax.xml.xpath.XPathExpressionException;
50 import javax.xml.xpath.XPathFactory;
51 import net.bionicmessage.extutils.Base64;
52 import org.w3c.dom.Document;
53 import org.w3c.dom.Node;
54 import org.w3c.dom.NodeList;
55 import org.xml.sax.InputSource;
56 import org.xml.sax.SAXException;
59 public class CalDAVExtensions implements IDAVHandler,ServerDriver {
61 private URL davServer;
62 private String base64cache = null;
63 private Logger log = null;
64 /* XPath setup */
65 private DocumentBuilderFactory dbf = null;
66 private DocumentBuilder dbuilder = null;
67 private boolean ready = false;
68 private Exception retException = null;
70 private groupDAV parent = null;
71 private static final NamespaceContext nsctx = new NamespaceContext() {
73 public String getNamespaceURI(String prefix) {
74 if ("D".equals(prefix)) {
75 return "DAV:";
77 return "";
79 // Stub
80 public Iterator getPrefixes(String val) {
81 return null;
83 // Stub
84 public String getPrefix(String uri) {
85 return null;
88 private XPathFactory xpf = null;
89 private XPath xp = null;
90 private XPathExpression pull_hrefs = null;
91 private XPathExpression pull_etags = null;
92 /** Constant string with CalDAV calendar filter syntax. Use
93 * with String.format(..)
94 * Parameters: Meta component type, iCal component type, iCal param,
95 * ical Value one, iCal Value 2
96 * See http://www.apps.ietf.org/rfc/rfc4791.html#sec-7.8.5
98 public static final String CALDAV_DISCOVER_BY_TIME =
99 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
100 "<C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\">" +
101 "<D:prop xmlns:D=\"DAV:\">\r\n<D:getetag/>\r\n" +
102 "</D:prop>\r\n<C:filter><C:prop-filter name=\"%s\">\r\n" +
103 "<C:prop-filter name=\"%s\">\r\n" +
104 "<C:time-range start=\"%s\" end=\"%s\"/>\r\n" +
105 "</C:prop-filter></C:comp-filter></C:prop-filter></C:filter>" +
106 "</C:calendar-query>";
107 public static final String CALDAV_DISCOVER_BY_TIMERANGE =
108 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
109 "<C:calendar-query xmlns:D=\"DAV:\"" +
110 " xmlns:C=\"urn:ietf:params:xml:ns:caldav\">" +
111 "<D:prop><D:getetag/></D:prop><C:filter><C:comp-filter name=\"VCALENDAR\">" +
112 "<C:comp-filter name=\"%s\"><C:time-range start=\"%s\"" +
113 " end=\"%s\"/></C:comp-filter>" +
114 "</C:comp-filter>" +
115 "</C:filter></C:calendar-query>";
117 public static final String CALDAV_PULL_EVERYTHING =
118 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
119 "<C:calendar-multiget xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">" +
120 "<D:prop><D:getetag/><C:calendar-data/></D:prop></C:calendar-multiget>";
122 /* public CalDAVExtensions(URL server, Logger l, String user, String pass) {
123 String up = user + ":" + pass;
124 String up64 = Base64.encodeBytes(up.getBytes());
125 String header = "Authorization: Basic" + up64;
126 init(server, header, l);
127 } */
129 public CalDAVExtensions(URL server, groupDAV parent, Logger l) {
130 init(server, parent, l);
133 public void init(URL server, groupDAV parent, Logger l) {
134 davServer = server;
135 this.parent = parent;
136 log = l;
137 dbf = DocumentBuilderFactory.newInstance();
138 dbf.setNamespaceAware(true);
139 try {
140 dbuilder = dbf.newDocumentBuilder();
141 xpf = XPathFactory.newInstance();
142 xp = xpf.newXPath();
143 xp.setNamespaceContext(nsctx);
144 pull_hrefs = xp.compile("//D:href/text()");
145 pull_etags = xp.compile("//D:getetag/text()");
146 } catch (Exception e) {
147 l.info("Exception: " + e.getMessage());
151 public boolean doesSupportCalDAV(URL fullURL)
152 throws Exception {
153 // Build an OPTIONS query
154 byte[] query = Common.buildQuery("OPTIONS", fullURL, new byte[0], null, parent);
155 String response = Common.sendNonKeepAliveRequest(davServer, query, parent, log);
156 GroupDAVObject rbo = new GroupDAVObject(response, GroupDAVObject.OBJECT_GET);
157 parent.addToCookieJar(rbo.getCookies());
158 String davHeader = (String) rbo.getHeaders().get("dav");
159 if (davHeader == null)
160 return false;
161 return davHeader.contains("calendar-access");
164 public void doBasicReport(URL fullURL)
165 throws Exception {
166 byte[] query = Common.buildQuery("REPORT", fullURL, new byte[0], null, parent);
167 String response = Common.sendNonKeepAliveRequest(davServer,query, parent, log);
168 GroupDAVObject rbo = new GroupDAVObject(response, GroupDAVObject.OBJECT_GET);
171 /** Queries the server for events specified by a particular time range
173 * @param fullURL The URL to the path being queried
174 * @param iComponent The type of component to query, i.e VCALENDAR or VTODO
175 * @param type The iCalendar parameter to compare, i.e DTSTART
176 * @param param1 The first date-time parameter
177 * @param param2 The second date time parameter
178 * @return a Map in the format in {url, etag}
179 * @throws java.io.UnsupportedEncodingException Rare - if your JRE can't do UTF-8
180 * @throws java.io.IOException due to network error
181 * @throws org.xml.sax.SAXException due to error parsing XML propfind
182 * @throws javax.xml.xpath.XPathExpressionException Due to error querying propfind.
184 public Map<String, String> getByTimeRange(URL fullURL,
185 String iComponent,
186 String type,
187 String param1,
188 String param2)
189 throws Exception {
190 /* String query = String.format(CALDAV_DISCOVER_BY_TIME,
191 iComponent,
192 type,
193 param1,
194 param2); */
195 String query = String.format(CALDAV_DISCOVER_BY_TIMERANGE,
196 iComponent,
197 param1,
198 param2);
199 Hashtable headers = new Hashtable();
200 headers.put("Depth", "1");
201 headers.put("Content-Type", "text/xml;charset=utf-8");
202 byte[] request = Common.buildQuery("REPORT",
203 fullURL,
204 query.getBytes("UTF-8"),
205 headers,
206 parent);
207 String response = Common.sendNonKeepAliveRequest(davServer, request,parent, log);
208 GroupDAVObject gbo = new GroupDAVObject(response, GroupDAVObject.OBJECT_GET);
209 parent.addToCookieJar(gbo.getCookies());
210 Document report = dbuilder.parse(
211 new ByteArrayInputStream(gbo.getContent().getBytes("UTF-8")));
212 NodeList hrefs = (NodeList) pull_hrefs.evaluate(report, XPathConstants.NODESET);
213 NodeList etags = (NodeList) pull_etags.evaluate(report, XPathConstants.NODESET);
214 HashMap<String, String> result_map = new HashMap(hrefs.getLength());
215 // Pool the above results together
216 for (int i = 0; i < hrefs.getLength(); i++) {
217 Node href = hrefs.item(i);
218 URL fullHref = Common.createURL(href.getNodeValue(), davServer);
219 Node etag = etags.item(i);
220 result_map.put(fullHref.toString(), etag.getNodeValue());
222 return result_map;
225 public void setLog(Logger log) {
226 this.log = log;
229 public List<GroupDAVObject> pullCalendarData(URL root) throws Exception {
230 HashMap headers = new HashMap();
231 headers.put("Depth","1");
232 headers.put("Content-Type", "text/xml;charset=utf-8");
233 byte[] data = CALDAV_PULL_EVERYTHING.getBytes("UTF-8");
234 byte[] req = Common.buildQuery("REPORT", root, data, headers,parent);
235 HTTPInputStream istr = Common.sendNonKeepAliveRequest(root, req);
236 ArrayList<GroupDAVObject> addTo = new ArrayList(500);
237 return extractFromReport(istr,addTo);
239 public List<GroupDAVObject> doMultiget(URL root, List<String> toDownload, List<GroupDAVObject> addTo) throws Exception {
240 StringBuffer request = new StringBuffer();
241 request.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
242 request.append("\r\n<C:calendar-multiget xmlns:D=\"DAV:\" \r\n");
243 request.append("xmlns:C=\"urn:ietf:params:xml:ns:caldav\"> ");
244 request.append("\r\n<D:prop>\r\n<D:getetag/>\r\n<C:calendar-data/>");
245 request.append("\r\n</D:prop>");
246 for(String s: toDownload) {
247 request.append("<D:href>");
248 request.append(s);
249 request.append("</D:href>");
251 request.append("</C:calendar-multiget>");
252 HashMap headers = new HashMap();
253 headers.put("Depth","1");
254 headers.put("Content-Type", "text/xml;charset=utf-8");
255 byte[] data = request.toString().getBytes("UTF-8");
256 byte[] req = Common.buildQuery("REPORT", root, data,headers, parent);
257 HTTPInputStream istr = Common.sendNonKeepAliveRequest(root, req);
258 return extractFromReport(istr, addTo);
261 public List<GroupDAVObject> extractFromReport(HTTPInputStream istr, List<GroupDAVObject> addTo) throws Exception {
262 InputSource is = new InputSource(istr);
263 ready = false;
264 SAXDAVHandler sdh = new SAXDAVHandler(this, is);
265 do {
266 // wait
267 } while(!ready);
268 if(retException != null) {
269 throw retException;
271 Stack<String> hrefStack = sdh.getHrefs();
272 Stack<String> etagStack = sdh.getEtags();
273 Stack<String> contentsStack = sdh.getContents();
274 while(!hrefStack.isEmpty()) {
275 String href = hrefStack.pop();
276 String etag = etagStack.pop();
277 String contents = contentsStack.pop();
278 GroupDAVObject child = new GroupDAVObject(istr, contents, etag, href);
279 addTo.add(child);
281 return addTo;
285 public boolean getReady() {
286 return ready;
289 public void setReady(boolean ready) {
290 this.ready = ready;
293 public void setRetException(Exception e) {
294 this.retException = e;