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(); } }