Annotated Controller Framework
With the annotations framework you use one or more singleton classes which act as controllers. These have methods marked with annotations which provide a certain operation (Eg GET, PUT) for a certain type of your domain object
For example, to support GET for an object of type Image you would use this:
@ResourceController public class ImagesController { @Get public InputStream getImageFile(Image image) throws IOException { File content = new File(image.getFileName()); return FileUtils.openInputStream(content); }
Annotated methods must always have as their first argument the object being operated on. This parameter is used as the key for deciding what method to call. Subsequent parameters depend on the operation being implemented. For example, a method which creates a new file requires parameters for the parent object to create the file in, the name of the file, and the data of the file as bytes or an inputstream.
Annotations come in these types:
- Method annotations. These permit some HTTP method to be executed on the given type. In some cases milton can implement certain HTTP methods without an explicitly annotated method. Eg LOCK
- Property annotations. These provide access to a certain property, such as modified date or the name of a resource. In most cases milton will look for sensible bean property names if there is no explicitly annotated method.
- Type annotations. These identify methods as returning objects which have a special significance, such as users, calendars
- Performance optimisation. These dont do any of the above, but allow better performance then without them. As such they are optional
When methods are called all of the relevant objects are put on a stack which is available to populate method arguments. Method arguments are matched by type, and then popped off the stack when they match. So if you have 2 parameters of the same type, the first parameter will be assigned the first object of that type on the stack. The order of parameters is defined below:
Full List of Annotations
Type | Annotation | Bean property names | Parameters | Notes |
---|---|---|---|---|
Property | @AccessControlList | TODO... | Used to get the list of Priviledges for all resources underneath the source object | |
Type | @AddressBooks | Marks a collection as one which contains address book objects. Often there will only be one address book. Must be directly under a user object | ||
@Authenticate | Marks a method as one which can authenticate a user with either a password or a Digest request | |||
Property | @CTag | ctag | Returns the "collection tag" for the source object. The CTag is required to change whenever the content of a member of the collection changes. This is used for syncronising calendars and address books with client devices | |
Property | @CalendarColor | color | Return the color used to identify a calendar | |
Type | @Calendars | Marks an object as one which contains calendars. Often there will only be one calendar in a collection of calendars. The calendars collection must be directly under a user object | ||
Optimisation | @ChildOf | Optional. Used as a performance optimised form of ChildrenOf to locate a single child of a collection without instantiating all of its siblings | ||
Method | @ChildrenOf | Find the logicial child objects of the requested source object. The presence of this method makes the source object a collection | ||
Property | @ContactData | vcard, contactData | Return the VCard formatted contact data for the requested contact object. | |
Property | @ContentLength | contentLength | Return the length of the GET'able representation of the source object. May return null. | |
Property | @ContentType | contentType | Return the content type of the GET'able repreentation of the source object | |
Method | @Copy | Make a copy of the source object with the given name and in the given parent object. Implementations should also make copies of child objects | ||
Property | @CreatedDate | createdDate | Return the created date for the source object | |
Method | @Delete | Delete the source object | ||
Property | @DisplayName | displayName, title | Return the display name of the source object. This is not required to be unique within the collection. Not used by most webdav clients so usually of little value | |
Method | @Get |
Output the GET'able representation of the source object. If you have returned a non-null ContentLength the length of the content actually output MUST be exactly the same. If a String is returned this is interpreted as a template name which is invoked using the ViewResolver. You may write content to an outputstream, or return an inputstream, or return a byte array. |
||
Property | @ICalData | ical, icalData | Return the calendar data for the source event object formatted as iCal text. An event object represents an event in a calendar. | |
Method | @MakeCollection | Create a collection with the given name inside the source object. You must return the newly created object | ||
Property | @MaxAge | maxAge | Return the maximum age in seconds that the source object's GET'able representation can be cached for. | |
Property | @ModifiedDate | modifiedDate | Return the modified date for the source object. This is used in caching, syncronisation and for producing etags, so its very important that this is accurate if implemented. Notably, it must change if the GET'able representation of the source object changes | |
Method | @Move | Move the source object to the given parent and with the given name. For renames the parent object will be the same as current. Usually, if a resource is moved to a new folder its name will not be changed. | ||
Property | @Name | name, fileName | Return the unencoded name of the resource. The encoded form will be used to construct a href to the resource. | |
Method |
@Post |
Executed on POST requests, it is up to the implementor to decide what should happen when this method is called. If a String is returned this is interpreted as a URL to redirect to. If a JsonResult object is returned it is rendered as JSON in the response. Otherwise the object is set onto the JsonResult data property, and the JsonResult is rendered in the response. |
||
Method | @PutChild |
Implements file upload, either by creating a new resource or updating an existing one. If not existing resource is found at the URL in the PUT, milton attempts to invoke an operation with the parent as the source object, followed by the data and the name for the new resource. If an existing resource is found at the URL specified in the PUT request milton will attempt to invoke the correponding PutChild method to update it. Otherwise the existing resource is deleted, then a new one created. |
||
@ResourceController | Marks a class as being a resource controller. Milton will parse the controller on startup and build a cache of controller methods | |||
Type | @Root | Marks a method as one which returns the root object for the given host. There must be exactly one of these methods, and it must return null or a single object. Returning null indicates that the host is not available on this server. | ||
Property | @UniqueId | id | Returns a value which uniquely identifies this resource. The value MUST NOT change as the content of the resource changes. It will be used as the identifier for locking, if locking is implemented, and will be used to contruct ETags which are often used in syncronisation and caching. | |
Type | @Users | Marks a method as one which returns user objects |
Authentication
The simplest thing is to just ignore authentication at first, it will fall through to the statically configured SimpleSecurityManager.
But if you do want to implement application authentication it has to be done with both @Authenticate and @Users, because The @Authenticate method is only called after a user object is found from your @Users method
So first this is called:
@ChildrenOf @Users // ties in with the @AccessControlList and @Authenticate methods below public List<Musician> getMusicians(MusiciansController root) { return Musician.findAll(SessionManager.session()); }
Milton will look for a member of that list with the same name as in the given credentials. Lets say the username is "beyonce", then it will find a Musician object where getName() equals "beyonce" (note this is where its good to implement @ChildOf as well, its faster then looking through the list)
Then milton will attempt to authenticate your Musician object by calling this to get the password:
@Authenticate public String getMusicianPassword(Musician m) { return m.getPassword(); // The @Authenticate also allows methods which verify a password and return Boolean }
Note that you might not be able to return a plain text password (ie if you only persist a hash of the password) so you can also use this form:
@Authenticate public Boolean verifyPassword(Musician m, String requestedPassword) { // calculate hash of requestedPassword and compare it to the real hash }
If you dont return a plain text password, and you want Digest auth, then you need to explicitly support Digest authentication...
@Authenticate public Boolean verifyDigestPassword(Musician m, DigestResponse digest) { // compared requested password digest with persisted password digest or plain text password }
Pre, Early and Late Authentication
Typically, in milton authentication is an operation on a resource, because the realm is a property of Resource and is needed to attempt authentication. We refer to this as "late" authentication because its performed only after the Resource which is the target of the request has been located. Late authentication can be problematic for certain implementations such as where an authenticated user context is required to access the repository, which creates a Catch-22 situation.
To resolve this dilemma Milton has supported "Pre" authentication, which is where authentication is performed prior to resource resolution. Unfortunately this approach uses a hack which is not compatible with the annotation framework. To use this approach you integrate the PreAuthenticationFilter into the milton stack, and also configure authentication handlers which dont require a Resource to work
A final option has been added for the annotations library and is between the Pre and Late authentication options and is referred to as Early Authentication. Using this option a Resource must still be located for authentication, but it doesnt need to be the actual target of the request, it could for example be a user's folder. This is indicated by using the @Principal parameter annotation to any annotated controlled method. If a @Principal parameter is encountered and no user has been authenticated then authentication will be attempted. If no credentials are available then a 401 challenge will be sent to the client forcing authentication.
Example
@ChildrenOf @Calendars public List<Calendar> getCalendars(CalendarsHome cals, @Principal Profile profile) throws NotAuthorizedException { return cals.user.getCalendars(); }
@PutChild public DataSession.FileNode createFile(Repository repo, String newName, InputStream inputStream, Request request, @Principal Profile user) throws IOException { DataSession dataSession = dataSessionManager.get(request, repo, true, user); return createFile(dataSession.getRootDataNode(), newName, inputStream, request, repo, user); }
Security with JAAS
Cyril Vringer has kindly provided a tutorial on integrating JAAS security with the annotations framework
Thanks Cyril!
Running Milton Webdav on Jetty, Tomcat and Glassfish with JAAS
It has been a long time since i've written something. I was working on our DMS, playing around with another webdav possibility. Our DMS uses JackRabbit for webdav. Although it has been working fine for about 6 years now, it's been causing a lot of overhead. So i looked into Milton (http://milton.io) which should be a nice lightweight solution. One can map it's own model to Milton and thus enable webdav. For us, our model (in the database) is translated intro a nice webdav solution.
To get a feeling about Milton, you can download milton-anno-ref. It's a working implementation, and runs out of the box.
Note: To enable full Dav Level2 you need a (trial) license. Read more here: http://milton.io/about/license/index.html
Now, when you use Milton, you probably want to run it on Tomcat, or some application server. Maybe you also use something like JAAS.
So how do you configure Milton to not use it's own authentication mechanism, but the container managed one? I'll describe how you can use JAAS, and run it on Jetty, Tomcat or Glassfish(3.1.2.2). Here we go.