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

Next Article:

Authentication and the AbstractResource