Posting to a service
At this point, Albums and Performers are displayed, but there's only the test
data added by the ServiceRunner.
Data can be added to the application by submitting the new data to services.
I'll begin to demo this by implementing the create link on the album listing within
the performer page, which calls the create operation within the album page, which
will submit data to the createAlbum method within the AlbumService.
Checking Permissions
While we want everyone to be able to see the music library data, we only
want certain people to be able to edit it. The permission to add an album is enforced
at the service level as specified in the service declaration:
@RequiresPermission("modify")
public Album createAlbum(@ParamName("title") String title, @ParamName("performer") Performer performer);
However, it doesn't make much sense to show someone something they can't do, so the
create link can be wrapped in a permission check, which only shows the create ability
to those that can actually create:
Albums:
<xsl:if test="java:hasPermission($user,'doc.walkthru.AlbumService.modify')">
[<a href="album.page?op=create&performer_key={result/@key}&performer_type={result/@type}">create</a>]<p/>
</xsl:if>
Where not logged in, the anonymous user is not shown the create link:
When a user with the doc.walkthru.AlbumService.modify permission is logged
in, they see the create link:
The checking of permissions within page render is good practice for atleast a couple of
reasons:
- The user only sees what the user can do, reducing clutter and increasing visibility
- There is less way for a user to know something they can't do is possible, and as such,
there is less likely to be attempts to hack into un-authorized functionality. If permissions
are setup correctly, the security api will deny access, but they're likely to find holes
if permissions are not setup quite correctly.
Create the service form
The create operation within the album page can now be created. The method we want to call
is:
@RequiresPermission("modify")
public Album createAlbum(@ParamName("title") String title, @ParamName("performer") Performer performer);
within the AlbumService. After requesting the performer from the service for display,
the following doCreate template is created to submit data to the method:
<xsl:template match="doCreate">
<form method="post" action="/svc/doc.walkthru.AlbumService">
<input type="hidden" name="exec" value="createAlbum(String,Performer)"/>
<input type="hidden" name="redirect" value="?op=edit&key=[result/key]"/>
<input type="hidden" name="performer_type" value="{result/@type}"/>
<input type="hidden" name="performer_key" value="{result/@key}"/>
<table class="format">
<tr>
<td align="right">Performer:</td>
<td>
<a href="performer.page?key={result/@key}&type={result/@type}">
<xsl:value-of select="result/name"/>
</a>
</td>
</tr>
<tr>
<td align="right">Title:</td>
<td><input type="text" name="title"/></td>
</tr>
<tr>
<td align="right" colspan="2" style="padding-top: 10px">
<input type="button" value="Cancel" onClick="window.history.go(-1)"/>
<input class="default" type="submit" value="Create Album"/>
</td>
</tr>
</table>
</form>
</xsl:template>
A form is created which calls /svc/doc.walkthru.AlbumService. This is
the web accessible url for the AlbumService. All accessible services
may be accessed at /svc/. When accessed with a browser, it presents
all available services and methods within a javadoc-like interface. All access
to services and methods within /svc/ goes through the security
API, so that users can only access what they are intended to access. The form should
have a post method.
Specify the method
The form has a hidden exec parameter, which
specifies the method to invoke
within the destination service (including parameter classes). The full name
of the method is specified (including parameter classes). The @ParamName
annotation values within the service are used to address the specific parameters.
Specify a redirect
The hidden redirect parameter specifies a url that is to be gone to after the service
method is invoked. In this example:
<input type="hidden" name="redirect" value="?op=edit&key=[result/key]"/>
the edit operation within the submitting page is called. If [result/<fieldName>]
is specified, it is replaced with the value of the given field (as declared in the Java class)
of method result object. This, of course, can only be used with methods which return
something. Using it with a method returning void results in an error.
In the example, the method returns an Album and the value of the key
field will replace [result/key]. If the method were run and return
a new album with a key of 123, the browser would be redirected to ?op=edit&123.
If no redirect input were given, the service would redirect to the
current page (via the referrer header). If you want to go to another page (or
operation within the page), specify a redirect input.
Provide method parameters
The parameters are now specified. The names (or prefixes) of the form inputs must
match with the @ParamName annotations as given in the service interface.
These @ParamName values are used to identify which parameter is being addressed.
First, the performer parameter is specified, which is a complex object (ie not a simple
type, such as String, int, Boolean, etc). There are three ways to submit complex objects:
- The easiest way, and the one shown here, is used when referring to an object which
exists in the repository. Two parameters are created for the method parameter. The
first is
[paramName]_type which specifies the Class to be fetched
from the repository. The second is [paramName]_key, which is the key
of the object (of the given class) to retrieve. The object is then retrieved and
used as the value for the parameter.
The [paramName]_type is not
strictly needed. If the method parameter class is an implementation type (ie a
non-abstract, non-interface class) that type itself will be used, if
no [paramName]_type is given. Note that classes must have a zero
parameter constructor (default, public, private or protected) in order to be constructed.
In this example, the type is specified as performer_type, which is either a GroupImpl
or ArtistImpl. The performer parameter class is a Performer,
which is an interface (not an implementation), so the performer_type
parameter is required. The performer_key holds the key for the performer. When the
form is submitted, the performer (be it a GroupImpl or an ArtistImpl) with the given
key is fetched from the repository and used as the performer parameter.
- The second way of submitting a complex object parameter is to specify a type and fields.
The type is specified in the same way, as
[paramName]_type. The field values
are then specified as [paramName]_[fieldName], where [fieldName]
is the name of the field, as declared in the Java class. The string value of the
field is converted to a Java object (be it to a String, Date, URL, int, etc) upon submission.
When the form is submitted, an object of the given type is instantiated and the
given fields are converted to the appropriate representation and assigned to the instance.
The instance is then used as the parameter to the method.
This way is good to use when the object is not in the repository and is small (ie
does not contain many fields). It's quick and easy to implement.
- The final way to submit a complex parameter is to submit it as XML. The XML
will be unmarshalled to the object and the object used as the parameter. It is
critical that the XML be formatted appropriately and include the
type
attribute (for interfaces or abstract class parameters). For example, the complex object
Widget could be added via:
<form method="post" action="/svc/com.some.WidgetService">
<input type="hidden" name="exec" value="addWidget(Widget)"/>
<textarea style="display: none" name="widget">
<widget type="com.some.WidgetImpl">
<height>10</height>
<width>20</width>
</widget>
</textarea>
<input type="submit"/>
</form>
On submission, a Widget would be unmarshalled from the XML in the widget textarea and
used as the value for the method parameter. XML submission can be quite useful for
more complex situations. We'll see this again when
JavaScript is used to call services.
The title for the album is specified simply by naming the input 'title'.
With the title and performer being specified, the submit of the form causes
the parameters to be received and instantiated (as required for the method), then
the service method to be invoked, the processing done and the result returned.
We now have an empty album done by a performer. The browser has been redirected
to album.page?op=edit and the album is ready to have tracks added.
In the next section, we'll use JavaScript
to populate the album.