Service!
- a service interface definition
- a service implementation
- if model objects will be passed to/from the service, they need to be annotated with @Portable
Service Interface
import org.jboss.errai.bus.server.annotations.Remote;
import org.uberfire.component.model.TasksRoot;
@Remote
public interface UFTasksService {
TasksRoot load(String userId);
String save(TasksRoot tasksRoot, String userId);
}
Here we have defined two methods, load and save – their purpose should be obious. Note that since this service will be passing a TasksRoot model object back and forth, the TasksRoot class must be annotated with @Portable (see below).
Service Implementation
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.marshalling.client.Marshalling;
import org.uberfire.backend.vfs.Path;
import org.uberfire.backend.vfs.PathFactory;
import org.uberfire.backend.vfs.VFSService;
import org.uberfire.component.model.TasksRoot;
import org.uberfire.component.service.UFTasksService;
@Service
@ApplicationScoped
public class UFTasksServiceImpl implements UFTasksService {
private final static String FILENAME = "tasks.json";
private final static String DEFAULT_URI = "default://uftasks";
@Inject
protected VFSService vfsServices;
@Override
public TasksRoot load(String userId) {
String uri = DEFAULT_URI + "/" + userId + "/" + FILENAME;
Path path = PathFactory.newPath(FILENAME, uri);
String content = vfsServices.readAllString(path);
TasksRoot tasksRoot = Marshalling.fromJSON(content, TasksRoot.class);
return tasksRoot;
}
@Override
public String save(TasksRoot tasksRoot, String userId) {
String content = Marshalling.toJSON(tasksRoot);
String uri = DEFAULT_URI + "/" + userId + "/" + FILENAME;
Path path = PathFactory.newPath(FILENAME, uri);
path = vfsServices.write(path, content);
if (path!=null)
return path.getFileName();
return null;
}
}
Notice here that since this service is being executed on the server side, the VFSService is a “peer” and can be called directly instead of having to use a Callback. Recall that, on the client side the client had to invoke VFSService asynchronously because information was sent over the wire from client to server and then back to client.
Model Objects
import java.util.ArrayList;
import java.util.List;
import org.jboss.errai.common.client.api.annotations.Portable;
@Portable
public class TasksRoot {
private Listprojects = new ArrayList ();
public TasksRoot() {
}
public ListgetProjects() {
return projects;
}
}
Errai uses Java reflection to traverse the object’s class definition. This means that any class fields that are not primitive types, or simple java List types must also be annotated as @Portable – in our case the Project, Folder, Task and TreeNode class definitions.
Errai does include an extensive marshalling framework, and we could provide our own custom serialization and deserialization routines instead of relying on Java reflection, but that’s a more advanced topic that I may cover later.
Client-side Changes
Now that we have our UFTasks service, we can use it on the client-side. Recall that the ProjectsPresenter did all of the task loading and saving before. This code now simply becomes:
@ApplicationScoped
@WorkbenchScreen(identifier = "ProjectsPresenter")
public class ProjectsPresenter {
....
@Inject
Caller<UFTasksService> ufTasksService;
....
private void loadTasksRoot() {
ufTasksService.call(new RemoteCallback<TasksRoot>() {
@Override
public void callback(final TasksRoot response) {
if (response!=null)
tasksRoot = response;
else
GWT.log("UFTasksService is unable to load tasks file");
updateView();
}
}).load(user.getIdentifier());
}
private void saveTasksRoot() {
ufTasksService.call(new RemoteCallback<String>() {
@Override
public void callback(final String response) {
GWT.log("Write Response: " + response);
}
}).save(tasksRoot, user.getIdentifier());
}
Everything else stays the same.
Reorganizing UFTasks
If you have read through the Uberfire documentation, you should already be familiar with the layout of the Uberfire Archetype. To summarize, the structure looks something like this:
- bom: “bill of materials” of the archetype. It defines the versions of all the artifacts that will be created in the library
- parent-with-dependencies: declares all dependencies and versions of the archetype.
- component: the uberfire component project containing server-side, client-side and common components.
- showcase: uberfire showcase directory containing the web app and distribution-wars.
A Closer Look at Errai Marshalling
The first thing you’ll notice when running this version of UFTasks is that the original JSON file developed in Part 4 is no longer compatible with this version of the app. That’s because, as I mentioned earlier, Errai uses Java reflection to figure out the structure of the model object being serialized. What you’ll see after creating a new uftasks.json file is something like this:
{
"^EncodedType": "org.uberfire.component.model.TasksRoot",
"^ObjectID": "1",
"projects": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "2",
"^Value": [
{
"^EncodedType": "org.uberfire.component.model.Project",
"^ObjectID": "3",
"name": "p1",
"selected": true,
"parent": null,
"children": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "4",
"^Value": [
{
"^EncodedType": "org.uberfire.component.model.Folder",
"^ObjectID": "5",
"name": "f1",
"parent": {
"^EncodedType": "org.uberfire.component.model.Project",
"^ObjectID": "3"
},
"children": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "6",
"^Value": [
{
"^EncodedType": "org.uberfire.component.model.Task",
"^ObjectID": "7",
"name": "t1",
"done": false,
"parent": {
"^EncodedType": "org.uberfire.component.model.Folder",
"^ObjectID": "5"
},
"children": {
"^EncodedType": "java.util.ArrayList",
"^ObjectID": "8",
"^Value": []
}
}
]
}
}
]
}
}
]
}
}