Commit fb5f118c authored by Ricki Hirner's avatar Ricki Hirner

Property registry

parent 9180f8b3
package at.bitfire.dav4android;
import com.squareup.okhttp.Authenticator;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import junit.framework.TestCase;
import java.io.IOException;
import java.net.Proxy;
import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.exception.InvalidDavResponseException;
import at.bitfire.dav4android.property.CalendarColor;
import at.bitfire.dav4android.property.DisplayName;
public class DavResourceTest extends TestCase {
......@@ -130,20 +136,39 @@ public class DavResourceTest extends TestCase {
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<multistatus xmlns='DAV:'>" +
" <response>" +
" <href>/dav</href>" +
" <propstat>" +
" <prop>" +
" <displayname>My DAV Collection</displayname>" +
" </prop>" +
" <status>HTTP/1.1 200 OK</status>" +
" </propstat>" +
" </response>" +
"</multistatus>"));
" <response>" +
" <href>/dav</href>" +
" <propstat>" +
" <prop>" +
" <displayname>My DAV Collection</displayname>" +
" </prop>" +
" <status>HTTP/1.1 200 OK</status>" +
" </propstat>" +
" </response>" +
"</multistatus>"));
dav.propfind(DisplayName.NAME);
// TODO assert zero members, but changed properties
// TODO hrefs with :, @ etc.
}
public void testPublicPropfind() throws IOException, HttpException, DavException {
httpClient.setAuthenticator(new Authenticator() {
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
String credential = Credentials.basic("test", "test");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
@Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return null;
}
});
DavResource dav = new DavResource(httpClient, HttpUrl.parse("https://demo.owncloud.org/remote.php/caldav/calendars/test/personal"));
dav.propfind(DisplayName.NAME, CalendarColor.NAME);
}
}
......@@ -2,6 +2,6 @@ package at.bitfire.dav4android;
public class Constants {
static final String LOG_TAG = "dav4android";
public static final String LOG_TAG = "dav4android";
}
......@@ -37,13 +37,15 @@ import lombok.SneakyThrows;
@RequiredArgsConstructor
public class DavResource {
final public MediaType MEDIA_TYPE_XML = MediaType.parse("application/xml; charset=utf-8");
public final MediaType MEDIA_TYPE_XML = MediaType.parse("application/xml; charset=utf-8");
final protected OkHttpClient httpClient;
protected final OkHttpClient httpClient;
final HttpUrl location;
final PropertyCollection properties = new PropertyCollection();
static private PropertyRegistry registry = PropertyRegistry.DEFAULT;
@SneakyThrows(XmlPullParserException.class)
public void propfind(Property.Name... reqProp) throws IOException, HttpException, DavException {
......@@ -66,6 +68,7 @@ public class DavResource {
Response response = httpClient.newCall(new Request.Builder()
.url(location)
.method("PROPFIND", RequestBody.create(MEDIA_TYPE_XML, writer.toString()))
.header("Depth", "0")
.build()).execute();
checkStatus(response);
......@@ -155,6 +158,7 @@ public class DavResource {
HttpUrl href = null;
StatusLine status = null;
PropertyCollection properties = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
......@@ -169,7 +173,9 @@ public class DavResource {
status = StatusLine.parse(parser.nextText());
break;
case "propstat":
parseMultiStatus_PropStat(parser);
PropertyCollection prop = parseMultiStatus_PropStat(parser);
if (prop != null)
properties = prop;
break;
case "location":
throw new UnsupportedDavException("Redirected child resources are not supported yet");
......@@ -188,14 +194,15 @@ public class DavResource {
// treat an HTTP error of a single response (i.e. requested resource or a member) like an HTTP error of the requested resource
checkStatus(status);
Log.d(Constants.LOG_TAG, "Received <response> for " + href + ", status: " + status);
Log.d(Constants.LOG_TAG, "Received <response> for " + href + ", status: " + status + ", properties: " + properties);
}
private void parseMultiStatus_PropStat(XmlPullParser parser) throws IOException, XmlPullParserException {
private PropertyCollection parseMultiStatus_PropStat(XmlPullParser parser) throws IOException, XmlPullParserException {
// <!ELEMENT propstat (prop, status, error?, responsedescription?) >
final int depth = parser.getDepth();
StatusLine status = null;
PropertyCollection prop = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
......@@ -204,7 +211,7 @@ public class DavResource {
if (XmlUtils.NS_WEBDAV.equals(ns))
switch (name) {
case "prop":
parseMultiStatus_Prop(parser);
prop = parseMultiStatus_Prop(parser);
break;
case "status":
status = StatusLine.parse(parser.nextText());
......@@ -214,23 +221,32 @@ public class DavResource {
eventType = parser.next();
}
if (status != null && status.code/100 == 2)
Log.i(Constants.LOG_TAG, "Received valid properties");
if (status == null || status.code/100 == 2)
return prop;
return null;
}
private void parseMultiStatus_Prop(XmlPullParser parser) throws IOException, XmlPullParserException {
private PropertyCollection parseMultiStatus_Prop(XmlPullParser parser) throws IOException, XmlPullParserException {
// <!ELEMENT prop ANY >
final int depth = parser.getDepth();
PropertyCollection prop = new PropertyCollection();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == depth+1) {
String ns = parser.getNamespace(), name = parser.getName();
// process property
Log.i(Constants.LOG_TAG, "Processing property " + ns + name);
Property.Name name = new Property.Name(parser.getNamespace(), parser.getName());
Property property = registry.create(name, parser);
if (property != null)
prop.put(name, property);
else
Log.i(Constants.LOG_TAG, "Ignoring unknown/unparseable property " + name);
}
eventType = parser.next();
}
return prop;
}
}
......@@ -16,6 +16,11 @@ public interface Property {
class Name {
public final String namespace;
public final String name;
@Override
public String toString() {
return name + "(" + namespace + ")";
}
}
}
......@@ -8,15 +8,69 @@
package at.bitfire.dav4android;
import android.text.TextUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.NonNull;
public class PropertyCollection {
Map<String, Map<String, Property>> properties = null;
protected Map<String, Map<String, Property>> properties = null;
Property get(@NonNull Property.Name name) {
if (properties == null)
return null;
Map<String, Property> nsProperties = properties.get(name.namespace);
if (nsProperties == null)
return null;
return nsProperties.get(name.name);
}
Map<Property.Name, Property> getMap() {
HashMap<Property.Name, Property> map = new HashMap<>();
if (properties != null) {
for (String namespace : properties.keySet()) {
Map<String, Property> nsProperties = properties.get(namespace);
for (String name : nsProperties.keySet())
map.put(new Property.Name(namespace, name), nsProperties.get(name));
}
}
return Collections.unmodifiableMap(map);
}
void put(Property.Name name, Property property) {
if (properties == null)
properties = new HashMap<>();
Map<String, Property> nsProperties = properties.get(name.namespace);
if (nsProperties == null)
properties.put(name.namespace, nsProperties = new HashMap<>());
nsProperties.put(name.name, property);
}
Property get(String namespace, String name) {
return null;
@Override
public String toString() {
if (properties == null)
return "[]";
else {
List<String> s = new LinkedList<>();
Map<Property.Name, Property> properties = getMap();
for (Property.Name name : properties.keySet()) {
s.add(name + ": " + properties.get(name));
}
return "[" + TextUtils.join(",\n", s) + "]";
}
}
}
package at.bitfire.dav4android;
import org.xmlpull.v1.XmlPullParser;
public interface PropertyFactory {
/**
* @return name of the Property the factory creates,
* e.g. Property.Name("DAV:", "displayname") if the factory creates DisplayName objects)
*/
Property.Name getName();
Property create(XmlPullParser parser);
}
package at.bitfire.dav4android;
import org.xmlpull.v1.XmlPullParser;
import java.util.HashMap;
import java.util.Map;
import at.bitfire.dav4android.property.CalendarColor;
import at.bitfire.dav4android.property.DisplayName;
public class PropertyRegistry {
protected Map<String, Map<String, PropertyFactory>> factories = new HashMap<>();
static final PropertyRegistry DEFAULT = new PropertyRegistry();
static {
DEFAULT.register(new DisplayName.Factory());
DEFAULT.register(new CalendarColor.Factory());
}
public void register(PropertyFactory factory) {
Property.Name name = factory.getName();
Map<String, PropertyFactory> nsFactories = factories.get(name.namespace);
if (nsFactories == null)
factories.put(name.namespace, nsFactories = new HashMap<>());
nsFactories.put(name.name, factory);
}
public Property create(Property.Name name, XmlPullParser parser) {
Map<String, PropertyFactory> map = factories.get(name.namespace);
if (map != null) {
PropertyFactory factory = map.get(name.name);
if (factory != null)
return factory.create(parser);
}
return null;
}
}
......@@ -8,7 +8,9 @@ import lombok.SneakyThrows;
public class XmlUtils {
public static final String NS_WEBDAV = "DAV:";
public static final String
NS_WEBDAV = "DAV:",
NS_APPLE_ICAL = "http://apple.com/ns/ical/";
private static final XmlPullParserFactory factory;
static {
......
package at.bitfire.dav4android.property;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import at.bitfire.dav4android.Constants;
import at.bitfire.dav4android.Property;
import at.bitfire.dav4android.PropertyFactory;
import at.bitfire.dav4android.XmlUtils;
import lombok.ToString;
@ToString
public class CalendarColor implements Property {
public static final Name NAME = new Name(XmlUtils.NS_APPLE_ICAL, "calendar-color");
public String color;
public static class Factory implements PropertyFactory {
@Override
public Name getName() {
return NAME;
}
@Override
public CalendarColor create(XmlPullParser parser) {
CalendarColor calendarColor = new CalendarColor();
try {
final int depth = parser.getDepth();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.TEXT && parser.getDepth() == depth) {
calendarColor.color = parser.getText();
} else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == depth)
break;
eventType = parser.next();
}
} catch(XmlPullParserException |IOException e) {
Log.e(Constants.LOG_TAG, "Couldn't parse <calendar-color>", e);
return null;
}
return calendarColor;
}
}
}
......@@ -8,11 +8,56 @@
package at.bitfire.dav4android.property;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import at.bitfire.dav4android.Constants;
import at.bitfire.dav4android.Property;
import at.bitfire.dav4android.PropertyFactory;
import at.bitfire.dav4android.XmlUtils;
import lombok.Getter;
import lombok.ToString;
@ToString
public class DisplayName implements Property {
public static final Name NAME = new Name(XmlUtils.NS_WEBDAV, "displayname");
public String displayName;
public static class Factory implements PropertyFactory {
@Override
public Name getName() {
return NAME;
}
@Override
public DisplayName create(XmlPullParser parser) {
DisplayName displayName = new DisplayName();
try {
// <!ELEMENT displayname (#PCDATA) >
final int depth = parser.getDepth();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.TEXT && parser.getDepth() == depth) {
displayName.displayName = parser.getText();
} else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == depth)
break;
eventType = parser.next();
}
} catch(XmlPullParserException|IOException e) {
Log.e(Constants.LOG_TAG, "Couldn't parse <displayname>", e);
return null;
}
return displayName;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment