public class Note {
private String description;
private String owner;
private String noteId;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getNoteId() {
return noteId;
}
public void setNoteId(String noteId) {
this.noteId = noteId;
}
}
How to Implement Custom RemoteBeans - CMCC 10
This article shows how to implement custom REST resources for Studio and invoke them using RemoteBeans. It covers the basic concepts behind the RemoteBeans, EntityResources and REST linking in Studio.
Prerequisites
You should be familiar with the CoreMedia Blueprint extension mechanism.
Also, you should have some basic Studio and Java development skills.
If you haven’t created a custom extension yet, take a look at the "external-library" extension.
The module contains the studio
module for the required ActionScript and the studio-lib
module for the Java part and can be re-used with some copy and paste and renaming. If you want to have a quick start, you can implement the following steps inside this extension too and create your custom extension later.
Implementing the Java Backend
Let’s start with implementing a so called EntityResource
class. An instance of EntityResource
is created for every RemoteBean
that is created in Studio via the call beanFactory.getRemoteBean(…)
.
EntityResources
are used when you have multiple elements of the same type in Studio, for example, the Content instances in Studio are created through EntityResources
. The same applies for messages of the notification API or CMS users and user groups.
In this example, you want to create entities that represent notes created by users. The user should be able to create, update and delete notes.
The note model would look like this:
You also need a representation class for this model (we explain the reason later).
public class NoteRepresentation {
private String description;
private String owner;
private String noteId;
NoteRepresentation(Note note) {
this.description = note.getDescription();
this.owner = note.getOwner();
this.noteId = note.getNoteId();
}
public String getDescription() {
return description;
}
public String getOwner() {
return owner;
}
public String getNoteId() {
return noteId;
}
}
So, you have a note with a description, an owner and an ID. You now have to create the EntityResource
class that wraps the REST operations around it:
@Produces(MediaType.APPLICATION_JSON)
@Path("notes/note/{id:[^/]+}")
public class NoteEntityResource implements EntityResource<Note> {
private String id;
@PathParam("id")
public void setId(@NonNull String id) {
this.id = id;
}
public String getId() {
return id;
}
@GET
public NoteRepresentation getRepresentation() {
return new NoteRepresentation(getEntity());
}
@DELETE
public boolean delete() {
return deleteNote(id);
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response setProperties(final Map<String, Object> rawJson) {
String description = (String) rawJson.get("description");
Note updatedNote = updateNote(id, description);
return Response.status(Response.Status.OK).entity(new NoteRepresentation(updatedNote)).type(MediaType.APPLICATION_JSON_TYPE).build();
}
@Override
public Note getEntity() {
return getNote(id);
}
@Override
public void setEntity(Note entity) {
this.id = entity.getNoteId();
}
private Note getNote(String id) {
//TODO
Note dummy = new Note();
dummy.setOwner("me");
dummy.setNoteId(id);
dummy.setDescription("I have to write a real storage for this!");
return dummy;
}
private boolean deleteNote(String id) {
//TODO
return false;
}
private Note updateNote(String id, String description) {
//TODO
Note note = getNote(id);
note.setDescription(description);
return note;
}
}
Now, have a look at the class NoteEntityResource
in detail:
@Produces(MediaType.APPLICATION_JSON)
@Path("notes/note/{id:[^/]+}")
The first two annotations are used to tell Spring what kind of bean you are creating. All response types have the JSON format and the @Path
annotation tells Spring under which URL the entity can be invoked. Note that the URL can have multiple path parameters. This example shows the most simple form with only one Id parameter. It is mandatory that every path parameter has a corresponding annotated setter method and a public getter too.
The class has one REST GET method:
@GET
public NoteRepresentation getRepresentation() {
return new NoteRepresentation(getEntity());
}
So when a GET is executed on this resource, the note NoteRepresentation
is returned and serialized to JSON. If the return format should differ from the originating model, you can freely customize the representation class. Because of the automatic REST linking, you must not return the same class here that has been defined as type of the EntityResource
! You can put models of other EntityResources
inside your representation as well. These entities will be converted to references during serialization. By this, different EntityResources
can be linked to each other. So you always have to create a representation class for the model that is bound for the EntityResource
. You just have to make sure that this representation contains the fields that should be supported by the RemoteBean `you
will implement.
Note that in this example, we won’t cover how and where these notes are stored. The helper methods at the end of the class have to be implemented properly to support a real data access layer.
Next, let’s add support for deletion by adding the following method:
@DELETE
public boolean delete() {
return deleteNote(id);
}
The method is pretty simple: if a DELETE request is executed in the resource, the corresponding helper is invoked and the note is deleted.
The same applies for updates:
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response setProperties(final Map<String, Object> rawJson) {
String description = (String) rawJson.get("description");
Note updatedNote = updateNote(id, description);
return Response.status(Response.Status.OK).entity(new NoteRepresentation(updatedNote)).type(MediaType.APPLICATION_JSON_TYPE).build();
}
You have finished the Java part now. Finally, you have to declare the entity via Spring XML in the corresponding component-*.xml
file:
<bean id="noteResource" class="com.coremedia.blueprint.studio.NoteEntityResource" scope="prototype" />
Make sure that the bean has the scope prototype since you have to create a new instance for each note.
You can rebuild the module and restart Studio now. The next steps can be implemented using the incremental Studio build that doesn’t require a Studio restart.
Implementing the Studio RemoteBeans
You are now able to create custom RemoteBeans
which are linked to the corresponding EntityResources
.
Every RemoteBean
consist of an interface and an implementing class. For our note model, the files Note.as
and NoteImpl.as
would look like:
public interface Note extends RemoteBean {
function getDescription():String;
function getUser():String;
function getNoteId():String;
}
}
with the implementing class:
[RestResource(uriTemplate="notes/note/{id:[^/]+}")]
public class NoteImpl extends RemoteBeanImpl implements Note {
public function NoteImpl(uri:String) {
super(uri);
}
public function getDescription():String {
return get('description');
}
public function getUser():String {
return get('user');
}
public function getNoteId():String {
return get('noteId');
}
}
}
When implementing RemoteBeans
, you have to make sure that the URI path of the RemoteBean
described in the header [RestResource(uriTemplate="notes/note/{id:[^/]+}")]
matches the REST URL of the Java resource entity class.
In the last step, you have to tell Studio to register this class as a RemoteBean
. Studio comes with a plugin for that, so add the following line to the configuration section of your Studio plugin rules:
<editor:RegisterRestResource beanClass="{NoteImpl}"/>
You are now able to use your custom RemoteBean
within components to render a note’s description.
Testing the EntityResource
Before using the newly created RemoteBean
inside a component, let’s see if the REST request is actually working. You can test this by logging into Studio, open a new tab and invoking the following URL:
The result should look like this:
{
description: "I have to find a real storage for this!"
owner: "me"
noteId: "123"
}
The URL segment api/ is configured for all Studio REST resources and ensures that all REST requests are located under one unique segment. Since your example doesn’t need a specific id, the segment '123' can be replaced with any other value.
Using RemoteBeans (in Components)
So your EntityResource
is working and you have declared a RemoteBean
for it. Let’s invoke it from ActionScript now. You can use the base class of your Studio plugin rules (if available) or another any other component base class that is created just to quickly test your code.
var note:Note = beanFactory.getRemoteBean('notes/note/123') as Note;
(note as NoteImpl).load(function(loadedNote:Note):void {
trace('My note says: ' + loadedNote.getDescription());
});
Note that the invocation of the RemoteBean
is done without the 'api' segment. RemoteBeans have to be loaded manually or via ValueExpression
. Compile your workspace with this code and reload Studio. You should see the following message on your browser console:
AS3: My note says: I have to write a real storage for this!
Next, let’s use the RemoteBean
inside a component:
<DisplayField>
<plugins>
<ui:BindPropertyPlugin bindTo="{ValueExpressionFactory.create('description', beanFactory.getRemoteBean('notes/note/123'))}" />
</plugins>
</DisplayField>
This example creates a label which contains the description of your note. Usually RemoteBeans
are always accessed through ValueExpression
. The ValueExpression
is then responsible for loading the value out of the RemoteBean
.
REST Linking (Java)
The note example has shown how to create a custom RemoteBean
. However, in the real world you usually have to deal with a list of RemoteBeans
, so let’s improve your example by adding another EntityResource
that is responsible for loading a list of notes.
First, you have to create the required Java classes for this again. First, you have to declare the entity model:
public class NoteList {
private List<Note> notes = new ArrayList<>();
public List<Note> getNotes() {
return notes;
}
public void setNotes(List<Note> notes) {
this.notes = notes;
}
}
Next, the matching representation which looks the same again:
public class NotesRepresentation {
private List<Note> notes;
public NotesRepresentation(NoteList noteList) {
this.notes = noteList.getNotes();
}
public List<Note> getNotes() {
return notes;
}
}
So you can create the EntityResource
from it:
@Produces(MediaType.APPLICATION_JSON)
@Path("notes")
public class NotesEntityResource implements EntityResource<NoteList> {
@GET
public NotesRepresentation getRepresentation() {
return new NotesRepresentation(getNoteList());
}
@Override
public NoteList getEntity() {
return getNoteList();
}
@Override
public void setEntity(NoteList entity) {
//no used
}
private NoteList getNoteList() {
NoteList list = new NoteList();
Note dummy1 = new Note();
dummy1.setOwner("me");
dummy1.setNoteId("1");
dummy1.setDescription("I have to write a real storage for this!");
Note dummy2 = new Note();
dummy2.setOwner("me");
dummy2.setNoteId("2");
dummy2.setDescription("And a lot of other stuff too!");
list.getNotes().add(dummy1);
list.getNotes().add(dummy2);
return list;
}
}
The example returns a list with two notes. In the last step, you have to add the Spring configuration for this resource entity into the corresponding XML file:
<bean id="notesResource" class="com.coremedia.blueprint.studio.NotesEntityResource" scope="prototype" />
Again, the Java part is finished, and you can rebuild the extension and restart Studio.
REST Linking (ActionScript)
Since you have created another EntityResource
, you have to declare the matching RemoteBeans
the same way your already did for the Note RemoteBean. That means you have to declare the interface
public interface Notes extends RemoteBean {
function getNotes():Array;
}
}
and the implementing class:
[RestResource(uriTemplate="notes")]
public class NotesImpl extends RemoteBeanImpl implements Notes {
public function NotesImpl(uri:String) {
super(uri);
}
public function getNotes():Array {
return get('notes');
}
}
}
Finally, tell Studio that a new RemoteBean
type is there. So, the configuration section of your custom Studio plugin would look like:
<editor:configuration>
<editor:RegisterRestResource beanClass="{NoteImpl}"/>
<editor:RegisterRestResource beanClass="{NotesImpl}"/>
</editor:configuration>
Rebuild and reload Studio. Once you are logged in, let’s test the new REST resource manually by invoking the following URL in another browser tab:
As a result you should see the following:
{
notes:
[
{
$Ref: "notes/note/1"
},
{
$Ref: "notes/note/2"
}
]
}
Note that not the plain JSON of the entities is serialized, but the references to them instead. For every class that is part of a representation a lookup is made if there is a corresponding EntityResource
declared for it. If true, the link to this resource is serialized instead of the linked entity.
Let’s invoke this inside ActionScript:
var notes:Notes = beanFactory.getRemoteBean('notes') as Notes;
(notes as NotesImpl).load(function(loadedNotes:Notes):void {
trace('I have ' + loadedNotes.getNotes().length + ' notes');
});
The code looks like the previous example. You create and load the matching RemoteBean
and log the status of it to the console. Note that only the Notes bean has been loaded through this code. The child elements must be loaded separately, so to display everything you can do something like this:
var notes:Notes = beanFactory.getRemoteBean('notes') as Notes;
(notes as NotesImpl).load(function(loadedNotes:Notes):void {
trace('I have ' + loadedNotes.getNotes().length + ' notes');
loadedNotes.getNotes()[0].load(function(note1:Note):void {
trace(note1.getDescription());
});
loadedNotes.getNotes()[1].load(function(note2:Note):void {
trace(note2.getDescription());
});
});
The output will look like this:
AS3: I have 2 notes
AS3: I have to write a real storage for this!
AS3: I have to write a real storage for this!
But wait! Why does the output shows the Note created in class NoteEntityResource
and not the Notes that have been created in class NotesEntityResource
? This is because the aggregator entity NotesEntityResource
is only serializing the references to the notes and not their concrete values. Since you haven’t mapped the IDs for notes, you will still only see the single example note of the NoteEntityResource
.
Where to go from here?
The next steps would be to put the notes list into a corresponding BindListPlugin
to visualize it inside a combo box, data view, or grid panel. These steps aren’t covered here, and you should pick up the Studio Developer manual for this or look up examples inside the Blueprint code.