Fixed davical (with the test suite at least)
[jgroupdav.git] / src / main / java / net / bionicmessage / groupdav / GroupDAV2Server.java
blob6ff845d1db69ebd969f872895807dc6b54692b5c
1 /* GroupDAV2Server.java
2 * Copyright 2010 Mathew McBride
4 * Redistribution and use in source and binary forms, with or without modification, are
5 * permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright notice, this list of
8 * conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 * of conditions and the following disclaimer in the documentation and/or other materials
12 * provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY Mathew McBride ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 * The views and conclusions contained in the software and documentation are those of the
25 * authors and should not be interpreted as representing official policies, either expressed
26 * or implied, of Mathew McBride.
28 package net.bionicmessage.groupdav;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import net.bionicmessage.extutils.Base64;
37 import net.bionicmessage.groupdav.http.HttpDelete;
38 import net.bionicmessage.groupdav.http.HttpPropfind;
39 import org.apache.http.Header;
40 import org.apache.http.HttpEntity;
41 import org.apache.http.HttpHost;
42 import org.apache.http.HttpResponse;
43 import org.apache.http.auth.AuthScope;
44 import org.apache.http.auth.UsernamePasswordCredentials;
45 import org.apache.http.client.AuthCache;
46 import org.apache.http.client.methods.HttpGet;
47 import org.apache.http.client.methods.HttpPut;
48 import org.apache.http.entity.ByteArrayEntity;
49 import org.apache.http.entity.StringEntity;
50 import org.apache.http.impl.auth.BasicScheme;
51 import org.apache.http.impl.client.BasicAuthCache;
52 import org.apache.http.impl.client.DefaultHttpClient;
53 import org.apache.http.protocol.BasicHttpContext;
54 import org.apache.http.util.EntityUtils;
55 import org.xml.sax.InputSource;
57 public class GroupDAV2Server
58 implements DAVServer, IDAVHandler {
60 private static final String DAV_PROPFIND = "<?xml version=\"1.0\" " +
61 "encoding=\"utf-8\"?>\n<propfind xmlns=\"DAV:\"\n xmlns:I=\"urn:ietf:params:xml:ns:caldav\"\n" +
62 " xmlns:C=\"urn:ietf:params:xml:ns:carddav\"\n" +
63 " xmlns:G=\"http://groupdav.org/\"\n" +
64 " xmlns:A=\"http://calendarserver.org/ns/\">\n\t" +
65 "<prop>\n\t\t<displayname/>\n\t\t<resourcetype/>\n\t\t" +
66 "<getetag/>\n\t\t<getcontenttype/>\n\t\t<current-user-privilege-set/>" +
67 "\n\t\t<G:component-set/>\n\t\t<I:supported-calendar-component-set/>" +
68 "\n\t\t<I:supported-calendar-data/>\n\t\t<I:calendar-description/>" +
69 "\n\t\t<C:supported-address-data/>\n\t\t<C:addressbook-description/>" +
70 "\n\t\t<A:getctag/>\n\t</prop>\n</propfind>\n";
71 /* private static final String DAV_PROPFIND = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
72 + "<propfind xmlns=\"DAV:\"><prop><getetag/></prop></propfind>"; */
74 private URI serverURI = null;
75 private DefaultHttpClient httpClient = null;
76 private BasicHttpContext httpContext = null;
77 private HttpHost httpHost = null;
78 private SAXDAVHandler handler = null;
79 boolean ready = false;
80 private Exception exception = null;
82 public GroupDAV2Server() {
83 this.httpClient = new DefaultHttpClient();
84 this.httpContext = new BasicHttpContext();
87 public Map<String, String> listObjects(String url) throws Exception {
88 URI pathURI = this.serverURI.resolve(url);
89 HttpPropfind pf = new HttpPropfind(pathURI);
90 StringEntity he = new StringEntity(DAV_PROPFIND);
91 he.setContentType("text/xml");
92 pf.setEntity(he);
93 HttpResponse hr = this.httpClient.execute(this.httpHost, pf, this.httpContext);
94 InputSource is = new InputSource(hr.getEntity().getContent());
95 this.handler = new SAXDAVHandler(this, is);
96 while (!(this.ready));
97 this.ready = false;
98 hr.getEntity().consumeContent();
99 return this.handler.getEtagMap();
102 public List<GroupDAVObject> getObjects(final List<String> urls) {
103 ArrayList<GroupDAVObject> downloaded = new ArrayList(urls.size());
104 ArrayList<String> downloadBatch = getBatch(urls, 500);
105 while (downloadBatch.size() != 0) {
106 for (String s : downloadBatch) {
107 try {
108 GroupDAVObject gdo = downloadSingleObject(s);
109 downloaded.add(gdo);
110 } catch (Exception e) {
111 e.printStackTrace();
114 downloadBatch = getBatch(urls, 500);
116 return downloaded;
119 public GroupDAVObject modifyObject(String url, String etag, String ctype, byte[] newContents)
120 throws Exception {
121 URI pathURI = this.serverURI.resolve(url);
122 HttpPut put = new HttpPut(pathURI);
123 put.addHeader("If-Match", etag);
124 ByteArrayEntity entity = new ByteArrayEntity(newContents);
125 entity.setContentType(ctype);
126 entity.setContentEncoding("UTF-8");
127 put.setEntity(entity);
128 HttpResponse hr = this.httpClient.execute(this.httpHost, put, this.httpContext);
129 if (hr.getStatusLine().getStatusCode() != 204) {
130 return null;
132 if (hr.getEntity() != null) {
133 hr.getEntity().consumeContent();
135 GroupDAVObject gbo = new GroupDAVObject();
136 gbo.setStatus(hr.getStatusLine().getStatusCode());
137 return gbo;
140 public GroupDAVObject putObject(String url, String ctype, byte[] content) throws Exception {
141 URI pathURI = this.serverURI.resolve(url);
142 HttpPut put = new HttpPut(pathURI);
143 ByteArrayEntity entity = new ByteArrayEntity(content);
144 entity.setContentType(ctype);
145 entity.setContentEncoding("UTF-8");
146 put.setEntity(entity);
147 HttpResponse hr = this.httpClient.execute(this.httpHost, put, this.httpContext);
148 if (hr.getEntity() != null) {
149 hr.getEntity().consumeContent();
151 GroupDAVObject obj = new GroupDAVObject();
152 Header loc = hr.getFirstHeader("Location");
153 if (loc != null) {
154 obj.setLocation(loc.getValue());
155 } else {
156 obj.setLocation(url);
158 return obj;
161 public boolean deleteObject(String url, String etag) throws Exception {
162 URI pathURI = this.serverURI.resolve(url);
163 HttpDelete delete = new HttpDelete(pathURI);
164 delete.addHeader("If-Match", etag);
165 HttpResponse hr = this.httpClient.execute(this.httpHost, delete, this.httpContext);
166 if (hr.getEntity() != null) {
167 hr.getEntity().consumeContent();
170 return (hr.getStatusLine().getStatusCode() == 204);
173 public void setProperty(String name, Object value) {
174 throw new UnsupportedOperationException("Not supported yet.");
177 public void setAuthenticationBasic(String b64) {
178 byte[] decoded = Base64.decode(b64);
179 String up = new String(decoded);
180 AuthScope as = new AuthScope(this.serverURI.getHost(), this.serverURI.getPort());
181 UsernamePasswordCredentials upc = new UsernamePasswordCredentials(up);
183 this.httpClient.getCredentialsProvider().setCredentials(as, upc);
186 public void setAuthentication(String user, String pass) {
187 AuthScope as = new AuthScope(this.serverURI.getHost(), this.serverURI.getPort());
188 UsernamePasswordCredentials upc = new UsernamePasswordCredentials(user, pass);
190 this.httpClient.getCredentialsProvider().setCredentials(as, upc);
193 public void setServer(String server) throws URISyntaxException {
194 this.serverURI = new URI(server).parseServerAuthority();
195 int port = 80;
196 if (this.serverURI.getPort() != -1) {
197 port = this.serverURI.getPort();
199 if (("https".equals(this.serverURI.getScheme())) && (port == 80)) {
200 port = 443;
203 this.httpHost = new HttpHost(this.serverURI.getHost(), port, this.serverURI.getScheme());
204 AuthCache authCache = new BasicAuthCache();
206 BasicScheme basicAuth = new BasicScheme();
207 authCache.put(this.httpHost, basicAuth);
209 BasicHttpContext localcontext = new BasicHttpContext();
210 localcontext.setAttribute("http.auth.auth-cache", authCache);
213 public boolean getReady() {
214 return this.ready;
217 public void setReady(boolean ready) {
218 this.ready = ready;
221 public void setRetException(Exception e) {
222 this.exception = e;
225 private ArrayList<String> getBatch(List<String> collection, int size) {
226 ArrayList batch = new ArrayList(size);
227 int i = 0;
228 Iterator iterator = collection.iterator();
229 while ((iterator.hasNext()) && (i != size)) {
230 String url = (String) iterator.next();
231 batch.add(url);
232 iterator.remove();
233 ++i;
235 return batch;
238 public GroupDAVObject downloadSingleObject(String url) throws Exception {
239 URI pathURI = this.serverURI.resolve(url);
240 HttpGet hg = new HttpGet(pathURI);
241 HttpResponse hr = this.httpClient.execute(this.httpHost, hg, this.httpContext);
242 HttpEntity he = hr.getEntity();
243 byte[] data = EntityUtils.toByteArray(he);
244 Header etag = hr.getFirstHeader("ETag");
245 Header loc = hr.getFirstHeader("Location");
246 GroupDAVObject gdo = new GroupDAVObject();
247 if (loc != null) {
248 URI newLocation = this.serverURI.resolve(loc.getValue());
249 gdo.setLocation(newLocation.getPath());
250 } else {
251 gdo.setLocation(pathURI.getPath());
253 gdo.setEtag(etag.getValue());
254 gdo.setContent(data);
255 hr.getEntity().consumeContent();
256 return gdo;