Create the resource model
Now we'll create the milton resource classes to represnt users and contacts.
Representing users in Carddav
In normal webdav applications we don't care much about users. Milton just requires your authenticate method to return an Object. But Carddav is different - it does need information about the user, so we have a special interface.
The object you return from authenticate must implement CarddavPrincipal
public interface CardDavPrincipal extends DiscretePrincipal {
/**
* This is usually a single href which identifies the collection which
* contains the users addressbooks. This might be the user's own href.
*
*/
HrefList getAddressBookHomeSet();
/**
* Returns the URL of an address object resource that corresponds to the
* user represented by the principal.
*
*/
String getAddress();
}
Representing contacts and address books
In carddav each contact is a resource (ie with its own URL and supports GET, etc) and contacts must be contained within a special type of collection called an AddressBook. The corresponding milton interfaces are:
- ICalResource - for contact resources. You should also implement normal file operation interfaces like GetableResource, Deleteable.. etc
- AddressBookResource - for collections containing contacts. Typically a user will have one or more of these associated with their account
A carddav server needs to be everything a normal webdav server is, plus some special carddav stuff. So we need a normal browsable tree structure. In this example we'll have
- a RootResource which contains users
- each user will be represented by a collection which contains a single address book resource,
- and each address book displays the contacts. In this example we'll have the same list of contacts for any user
- each contact is effectively a file
RootResource.java
The RootResource just serves as an entry point to locate user objects:
public class RootResource extends AbstractResource implements CollectionResource {
private static org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(RootResource.class);
private ArrayList<Resource> children;
public RootResource(ContactManager contactManager) {
super(contactManager);
}
@Override
public List<? extends Resource> getChildren() {
if( children == null ) {
children = new ArrayList<Resource>();
for( ContactsDao.User g : contactManager.getContactsDao().getUsers()) {
children.add(new UserResource(g, this));
}
}
return children;
}
@Override
public Resource child(String childName) {
return ChildUtils.child(childName, getChildren());
}
@Override
public String getName() {
return "";
}
}
UserResource.java
The UserResource represents a user and is used for authentication. Note that the same instance which is returned by authenticate (to represent the current user) will be used as the webdav node to be browsed
public class UserResource extends AbstractResource implements CardDavPrincipal, CollectionResource{
private final ContactsDao.User user;
private ArrayList<Resource> children;
public UserResource(ContactsDao.User user, RootResource parent) {
super(parent, parent.contactManager);
this.user = user;
}
@Override
public String getName() {
return user.getUserName();
}
@Override
public HrefList getAddressBookHomeSet() {
return HrefList.asList(getHref() + "abs/"); // the address books folder
}
@Override
public String getAddress() {
return getHref() + "abs/";
}
@Override
public String getPrincipalURL() {
return getHref();
}
@Override
public PrincipleId getIdenitifer() {
return new HrefPrincipleId(getHref());
}
@Override
public Resource child(String childName) throws NotAuthorizedException, BadRequestException {
return ChildUtils.child(childName, getChildren());
}
@Override
public List<? extends Resource> getChildren() throws NotAuthorizedException, BadRequestException {
if( children == null ) {
children = new ArrayList<Resource>();
children.add(new UserAddressBookResource(this));
}
return children;
}
}
UserAddressBookResource.java
This class is created to hold a list of contacts. Note that many Carddav clients expect to be able to create new contact lists, but this is not supported in this example
public class UserAddressBookResource extends AbstractResource implements AddressBookResource, PutableResource {
private ArrayList<Resource> children;
public UserAddressBookResource(UserResource parent) {
super(parent, parent.contactManager);
}
@Override
public String getName() {
return "abs";
}
@Override
public Resource createNew(String newName, InputStream in, Long length, String contentType) throws IOException, ConflictException, NotAuthorizedException, BadRequestException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
IOUtils.copy(in, bout);
String icalData = bout.toString("UTF-8");
Contact cNew = contactManager.createContact(newName, icalData);
return new ContactResource(this, cNew);
}
@Override
public InternationalizedString getDescription() {
return new InternationalizedString(null, "Common addressbook for all users");
}
@Override
public void setDescription(InternationalizedString description) {
// not supported
}
@Override
public List<Pair<String, String>> getSupportedAddressData() {
AddressDataTypeList supportedAddresses = new AddressDataTypeList();
supportedAddresses.add(new Pair<String, String>("text/vcard", "3.0"));
return supportedAddresses;
}
@Override
public Long getMaxResourceSize() {
return Long.MAX_VALUE;
}
@Override
public String getCTag() {
return "v" + contactManager.getContactsDao().getContactsVersion();
}
@Override
public Resource child(String childName) throws NotAuthorizedException, BadRequestException {
return ChildUtils.child(childName, getChildren());
}
@Override
public List<? extends Resource> getChildren() throws NotAuthorizedException, BadRequestException {
if (children == null) {
children = new ArrayList<Resource>();
for (ContactsDao.Contact g : contactManager.getContactsDao().getContacts()) {
children.add(new ContactResource(this, g));
}
}
return children;
}
}
ContactResource.java
This represents each contact in a contact list. It should be individually Get'able amd Put'able and should also support MOVE, DELETE, COPY, etc ... but we've kept things simple here. See the Basic File Operations tutorial for how to implement those:
public class ContactResource extends AbstractResource implements GetableResource, ReplaceableResource, ICalResource {
private static org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(ContactResource.class);
private final UserAddressBookResource parent;
private final Contact contact;
public ContactResource(UserAddressBookResource parent, Contact contact) {
super(parent, parent.contactManager);
this.parent = parent;
this.contact = contact;
}
@Override
public void sendContent(OutputStream out, Range range, Map<String, String> params, String contentType) throws IOException {
// we assume that the icalData on the Person object is consistent with other properties
String ical = contact.getIcalData();
if( ical != null ) {
out.write(ical.getBytes("UTF-8"));
} else {
log.warn("ICAL data is null on resource: " + contact.getFileName());
}
}
@Override
public void replaceContent(InputStream in, Long length) throws BadRequestException, ConflictException, NotAuthorizedException {
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
IOUtils.copy(in, bout);
String icalData = bout.toString("UTF-8");
contactManager.update(contact, icalData);
} catch (IOException iOException) {
throw new RuntimeException(iOException);
}
}
@Override
public Long getContentLength() {
return null;
}
@Override
public String getContentType(String accept) {
return "text/vcard";
}
@Override
public String getName() {
return contact.getFileName();
}
@Override
public Long getMaxAgeSeconds(Auth auth) {
return null;
}
@Override
public String getICalData() {
return contact.getIcalData();
}
@Override
public String getUniqueId() {
return contact.getUid();
}
}