Developing Juzu portlets – Step 2: viewing and posting secrets
Previous step:
– Learn how to develop great Juzu portlets for eXo Platform!
In the previous blog post, you learned how to start a simple Juzu project and deploy it in eXo Platform.
In this blog post, we will start to play with Juzu and add some functionality to our application.
Introduction
It’s time to tell you a little bit more about the JuZcret application.
First of all we have secret. A secret is a simple message but it can also be enhanced by an image (to illustrate the secret better).
Our application will manage a list of secrets and display them. Of course any user can also share their secrets using an add secret button.
In later steps, we will allow connected users to like and comment on secrets, but these features are not included in Step 2.
Create our model
This does not require any Juzu stuff. We just need to create a simple Secret bean containing a default constructor with getters and setters.
In the org.juzu.tutorial package, add a new package named models. In this new package, create a Secret class as below:
Secret
package org.juzu.tutorial.models; import java.io.Serializable; import java.util.Date; public class Secret implements Serializable { private String message; private String imageURL; private Date createdDate; public Secret() {} public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getImageURL() { return imageURL; } public void setImageURL(String imageURL) { this.imageURL = imageURL; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } }
Create our service
We need to create an application bean to manage all the secrets: the SecretService!
The SecretService bean will contain all the logic for our application, like getting the list of secrets and adding a secret.
In the
package, add a new package named services. In this new package, create the interface SecretService and its implementation as below:The SecretService interface
package org.juzu.tutorial.services; import java.util.List; import org.juzu.tutorial.models.Secret; public interface SecretService { List<Secret> getSecrets(); void addSecret(String message, String imageUrl); }
The SecretService implementation
Note: In step 2, we will implement a simple in-memory data-saving implementation for simplicity. Real persistence is only for real aspirant Juzu developers not for rookies 😉 and will be covered later in step 5.
package org.juzu.tutorial.services; import org.juzu.tutorial.models.Secret; import java.util.Date; import java.util.List; public class SecretServiceMemImpl implements SecretService { private List<Secret> secretsList; @Override public List<Secret> getSecrets() { return secretsList; } @Override public void addSecret(String message, String imageUrl) { Secret secret = new Secret(); secret.setMessage(message); secret.setImageURL(imageUrl); secret.setCreatedDate(new Date()); secretsList.add(secret); } }
Display secrets
Now that our SecretService application bean is ready, we would like to use it.
But as before, we first need to declare it. And declaring a custom bean in Juzu is very simple.
Binding the application bean
Remember step 1, when I told you about the configuration file for our application named package-info.java.
This Java file is the home for the package level annotation. This is where we will declare our new application bean. Replace the file
with:@juzu.Application @Bindings({ @Binding(value = org.juzu.tutorial.services.SecretService.class, implementation = org.juzu.tutorial.services.SecretServiceMemImpl.class, scope = Scope.SINGLETON) }) package org.juzu.tutorial; import juzu.Scope; import juzu.plugin.binding.Binding; import juzu.plugin.binding.Bindings;
In a Juzu application, we can have several kinds of bean: controllers, templates, plugins and application services (don’t worry, each of these will be covered in a different step).
All these different types of bean are managed by the container, which means that it is the job of the IOC container to manage the service lifecycle (instantiation, inject dependencies, …) and to inject them where you need them.
For instance, you can directly inject and use a template in a controller by adding the @Inject annotation when you declare it:
@Inject @Path("index.gtmpl") Template index;
However, if you want to use a custom bean, like our SecretService, you first need to declare it in the
using the @Binding annotation.And that’s it. Now we can use the SecretService bean anywhere in our application, simply by using the
annotation.Scoped binding
It’s time for a lesson! Let’s talk a little bit about scoped binding in Juzu.
As you can see, we have declared our service as a singleton:
scope = Scope.SINGLETON
By declaring the service bean as a singleton in
, I have overridden the scope annotation the bean could declare.The annotation scope is optional in
. If the scope is not specified, it is determined from the bean, which should be annotated with a scope.For instance, in our case, the following declaration in
:@Bindings({ @Binding(value = org.juzu.tutorial.services.SecretService.class, implementation = org.juzu.tutorial.services.SecretServiceMemImpl.class, scope = Scope.SINGLETON) })
will give us the exact same result as this declaration:
@Bindings({ @Binding(value = org.juzu.tutorial.services.SecretService.class, implementation = org.juzu.tutorial.services.SecretServiceMemImpl.class) }) and add in SecretServiceMemImpl the @Singletion annotation: @Singleton public class SecretServiceMemImpl implements SecretService { ... }
It’s recommended to add a scope in package-info.java, since it gives more fine-grained control and on opening the file you have a quick overview of the composition of the project.
Abstract bean binding
Let’s have another talk, this time about abstract bean binding in Juzu.
In our example, we need to set the implementation member of the @Binding annotation because SecretService is an interface.
If an application bean doesn’t have an interface, then you don’t need to set the implementation member. For instance, in our case, we may directly use this implementation:
@Bindings({ @Binding(value = org.juzu.tutorial.services.SecretServiceImpl.class, scope = Scope.SINGLETON) })
Ok, that’s enough explanation. Let’s go back to the code.
Develop the controller
We already developed the new application bean, SecretService, and we declared it in package-info.java. Now it’s time to use it with a new controller named JuZcretApplication, which will allow us to display a list of secrets.
In the
, create a new java class JuZcretApplication:package org.juzu.tutorial; public class JuZcretApplication { }
The JuZcretApplication controller bean must be the default controller for our application. Right now we have two controller beans in our project:
There is no problem with this. In Juzu, you can have as many controllers as you want. But (and there’s always a but…), you need to tell Juzu which one is the default controller.
Guess where we will define this? Yes,
!Open it and just update the @Application annotation by setting the defaultController member:
@juzu.Application(defaultController = org.juzu.tutorial.JuZcretApplication.class)
To display a list of secrets, our JuZcretApplication controller needs:
- The SecretService application bean
- A new template able to display a list of secrets
Create a new empty template,
, in the package. The secretWall template will be responsible for displaying the list of secrets.Also create another empty template,
, in the package:The addSecret template will be responsible for displaying the form used to add a new secret.
We need to inject into JuZcretApplication, our application bean and our template beans:
package org.juzu.tutorial; import juzu.Path; import org.juzu.tutorial.services.SecretService; import javax.inject.Inject; public class JuZcretApplication { @Inject SecretService secretService; @Inject @Path("secretWall.gtmpl") org.juzu.tutorial.templates.secretWall secretWall; @Inject @Path("addSecret.gtmpl") org.juzu.tutorial.templates.addSecret addSecret; }
Note: If you get a “cannot resolve symbol” exception after injecting a newly created template, like this:
then you should recompile your project:
$ mvn clean compile
This will generate the Java class associated with this template and consequently fix the “cannot resolve symbol” exception.
Now we need to create a new view controller. A view controller is a method with the @View annotation and is responsible for providing the markup. @View will use an injected template to create markup. The @View method delegates the rendering to the template.
In our case, we will create a new view controller responsible for displaying the list of secrets.
Type safe parameters
You can declare parameters in a template that are directly available to subclasses of the
class.For instance, open
and add:#{param name=secretsList/} Here is my secret list: ${secretsList} #{param name=secretsList/} declare the parameter secretsList ${secretslist} display the parameter secretsList
Now add the view into JuZcretApplication.java like this:
import juzu.Response; import juzu.View; ... @View public Response.Content index() { return secretWall.with().secretsList("My list of secret").ok(); }
You see that we can directly set the parameter secretsList declared in the secretWall template via a generated method named with the parameter name. For the secretsList parameter
, we have a method that can be used.Consequently, if you modify the
template and change the name of the parameter , the compilation of will fail.Declaring a parameter in the template and using a generated method in the controller are not mandatory. You can simply use a HashMap to store parameters passed by the controller to the template:
@View public Response.Content index() { Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put("secretsList", "My list of secret"); return secretWall.with(parameters).ok(); }
But using a HashMap means that if a parameter name changes in the template, the controller will continue to compile without error because of the generic parameter map, and you will only see the error at runtime. To avoid seeing this situation, it’s better to declare parameters in the template and use the generated methods named with the parameter name.
Default controller method
One more thing. In Juzu, index is a special name that catches any unmatched request. In other words, the method index() annotated with @View provides the default markup for our application.
Integrating with Platform 4.1
Guys, after this explanation we need to take a break here because I have something to confess.
Today the official Juzu version supported by Platform 4.1 is 0.6.2. I don’t want to limit you to 0.6.2. I want to let you discover all of the Juzu features available in version 1.0.0-cr1. To run a Juzu 1.0.0-cr1 portlet properly in Platform 4.1, we need to override the implementation that comes from the eXo commons project. If not, we’ll get an unexpected error due to the conflict in the class version. This problem will be fixed as soon we upgrade Platform with the newest Juzu version.
This is quick and pretty simple. And because we are nice, notice that we created a specific Juzu helloWorld project for you that already contains all the stuff needed to create a Juzu portlet for Platform 4.1.
So for your next personal project, I advise you to clone this Juzu Platform 4.1 startup project.
But in this tutorial, it’s important to understand what happens so you can become a real Juzu developer ;). That’s why you need to follow this instruction:
In Platform, the services are managed by eXo container, thanks to provider factory support. A provider factory provides pluggability for integrating beans that are not managed natively by the IOC container but need to be integrated inside the container.
For JuZcret to run properly in Platform now, we need to declare a KernelProviderFactory.
First, we need to create a new file named
in .In this file, we just add a line with the implementation class name:
org.exoplatform.commons.juzu.KernelProviderFactory
Notice that it’s important to keep the name exactly like that above, as this is a workaround to override the implementation that comes from the eXo commons project.
Second, we create the kernel provider factory class. In the
package, add a KernelProviderFactory class:package org.exoplatform.commons.juzu; import javax.inject.Provider; import juzu.inject.ProviderFactory; import org.exoplatform.container.PortalContainer; import org.picocontainer.ComponentAdapter; public class KernelProviderFactory implements ProviderFactory { @Override public <T> Provider<? extends T> getProvider(final Class<T> implementationType) throws Exception { final PortalContainer container = PortalContainer.getInstance(); if (container == null) { throw new IllegalStateException("Not running in the context of a portal container"); } final ComponentAdapter adapter = container.getComponentAdapterOfType(implementationType); if (adapter != null) { return new Provider<T>() { @Override public T get() { Object service = adapter.getComponentInstance(container); if (service == null) { throw new RuntimeException("Could not obtain service " + implementationType + " from container " + container); } return implementationType.cast(service); } }; } else { return null; } } }
The provider calls the PortalContainer – an object from the eXo kernel library – and delegates finding the managed bean to it. Juzu will use the returned result to bind to its container. So we need to add the dependency to eXo kernel lib in the POM of our project:
<dependency> <groupId>org.exoplatform.kernel</groupId> <artifactId>exo.kernel.container</artifactId> <version>2.4.x-SNAPSHOT</version> <scope>provided</scope> <exclusions> <exclusion> <artifactId>servlet-api</artifactId> <groupId>javax.servlet</groupId> </exclusion> </exclusions> </dependency>
That’s it. Now our Juzu 1.0.0-cr1 portlet can work on Platform 4.1.
Remember, for your next project, a Juzu Platform 4.1 startup project is available.
Display secrets
Note: We will now configure our project to use JRebel. This will allow hot redeployment of our portlet without restarting the server.
Recompile the project:
$ mvn clean install
Copy and paste the WAR file (which replaces the old one) in the web app folder of the Platform server as explained in step 1, start the server and open the JuZcret page created in step 1.
It displays a simple message:
What we really want it to do is to get the list of secrets and display it. Not display a single hard-coded sentence.
Note: Remember that we have just configured the portlet to use JRebel. So from now on, we don’t need to restart the server after a modification. We can just rebuild it using mvn clean compile.
In
, modify the index method to pass a list of secrets to the secretWall template instead of a string:@View public Response.Content index() { return secretWall.with().secretsList(secretService.getSecrets()).ok(); }
In
, we will initialize the list with some fake secrets so that we have something to display for our test:import java.util.LinkedList; ... public class SecretServiceMemImpl implements SecretService { private List<Secret> secretsList; public List<Secret> getSecrets() { if (secretsList == null) { secretsList = new LinkedList<Secret>(); addFakeSecrets(); } return secretsList; } ... private void addFakeSecrets() { addSecret("Yesterday I said I missed my PL meeting because I have to many work. In fact I was drinking free beer in Barbetta pub", "https://c1.staticflickr.com/3/2385/2345543856_6d0fbafb66_z.jpg?zz=1"); addSecret("I have a master degree but I still use Google to calculate 3*8", "https://yy2.staticflickr.com/7244/7245177220_3f17ee9fb8_z.jpg"); addSecret("I am in relationship for 2 years. He is awesome, powerful and I never go out without him. His name is Linux", "https://fc02.deviantart.net/fs71/f/2009/364/9/d/christmas_love_by_skubaNiec.jpg"); addSecret("I spent 2 hours a day to train my cat to perform a backflip", "https://fc06.deviantart.net/fs15/i/2007/008/e/b/colour_cat_wallpaper_by_jellyplant.jpg"); addSecret("I pretend to be a spy when I go out. In reality my job is to perform photocopy at the embassy", "https://c2.staticflickr.com/2/1230/5108154392_3cc02cac67_z.jpg"); } }
Finally, we need to update the secretWall.gtmpl template to manage the list of secrets passed by the controller and display all secrets:
#{param name=secretsList/} <ul class="secret-wall-list"> <% secretsList.each { secret -> %> <li> ${secret.message} </li> <% } %> </ul>
Juzu templating
The native Juzu template engine extends the Groovy templating system so we can include snippets of Groovy code or resolve Groovy expressions.
In our case, we use Groovy code with the scriptlet syntax
to perform a simple loop for each secret. Then each secret is added within a tag using Groovy expressions wrapped with the syntax.See the list of secrets
Next we rebuild our project, refresh the browser, and here is the result:
You’ll see in the server log something like this:
JRebel: Reloading class 'org.juzu.tutorial.JuZcretApplication'. JRebel: Reloading class 'org.juzu.tutorial.templates.secretWall'. JRebel: Reloading class 'org.juzu.tutorial.services.SecretService'. JRebel: Reloading class 'org.juzu.tutorial.models.Secret'
JRebel reloads the classes, which saves us from having to restart the Tomcat server. But keep in mind that it can only reload a class; it can’t renew an object instance. That means that attributes of created objects stay the same after the class has been reloaded.
Add a secret
We are close to the end! After displaying the secrets, we want to add a new secret.
We already have a service ready with an addSecret method:
@Override public void addSecret(String message, String imageUrl) { Secret secret = new Secret(); secret.setMessage(message); secret.setImageURL(imageUrl); secret.setCreatedDate(new Date()); secretsList.add(secret); }
What is missing is a form to create a new secret and the logic to manage it. This is what we need to do:
- Update the gtmpl template for adding a secret.
- Update the gtmpl template to display an add secret form.
- Create a new view controller to provide markup for creating a secret using the gtmpl template.
- Add some logic to switch between the different views and manage the add secret feature.
Template
We need to add a link in the
to switch to the add secret form:#{param name=secretsList/} <ul class="secret-wall-list"> <% secretsList.each { secret -> %> <li> ${secret.message} </li> <% } %> </ul> <a href="#" role="button">Share my secret</a>
For now we will keep the href empty. We will come back to it later.
Then we need to update the
template so we can display an add secret form:<form action="#" method="POST" role="form"> <h5>Share my secret</h5> My secret: <textarea rows="3" name="msg" placeholder="Write your secret here"></textarea> <br/> Image URL: <input name="imgURL" placeholder="https://upload.wikimedia.org/wikipedia/commons/e/ee/Karl_Witkowski_-_Secrets.jpg"> <br/> <button type="submit">Share</button> </form>
We keep the values of both href and the action parameters empty. We will come back to these later.
View
We need to create a new view controller to provide markup for adding a new secret in
.At the beginning of this step, we have already injected the new template, addSecret:
@Inject @Path("addSecret.gtmpl") org.juzu.tutorial.templates.addSecret addSecret; Now we need to create the View method addSecretForm(): @View public Response.Content addSecretForm() { return addSecret.ok(); }
Go back to
to update the href of the link and let Juzu manage it:<a href="@{JuZcretApplication.addSecretForm()}" role="button">Share my secret</a>
Controller URLs are natively supported in the template. So if you change the path for your view, you don’t need to update your template. Juzu will take care of it.
Action
In Juzu, application logic processing is implemented via an action controller, which is a method annotated with @Action.
In
, let’s create our first action controller. It will be responsible for creating a new secret:import juzu.Action; ... @Action public Response.View addSecret(String msg, String imgURL) { secretService.addSecret(msg, imgURL); return JuZcretApplication_.index(); }
Now go back to
and update the submit action of the form:<form action="@{JuZcretApplication.addSecret()}" method="POST" role="form"> <h5>Share my secret</h5> ... </form>
Now rebuild the project, refresh the browser, and click on the Share my secret link to add a new secret:
Note: After JRebel reloads our project, our portlet may display an error message instead of the expected screen. If this happens, you need to restart the server.
Redirection
An action never produces markup. Instead an action phase is followed by a view phase that will return a markup response. Juzu handles this interaction with an http redirection to the next view phase via the redirect after post pattern.
What’s JuZcretApplication_? It’s a class generated by Juzu via an annotation of JuZcretApplication.
It’s the companion class of JuZcretApplication generated by Juzu during the compilation of the project. In Juzu, all controller classes generate a companion class. The companion class has the same name as the original class appended with the _ character.
So, after adding a new secret you are automatically redirected to the secret wall page:
Now we don’t need
and any more. You can remove both of them. Your project should look like this:We now have some interesting features allowing us to interact with our Juzu portlet, but it still has an ugly design…
In the next blog post, Step 3 – Building a sexy secret wall, we will improve the UI of JuZcret to attract many secret users… (Edit: “Step 3: Building a sexy secret wall” is live!)
The final source of step 2 is available for downloading on GitHub..
Previous and next steps:
– Learn how to develop great Juzu portlets for eXo Platform!
– Step 2: viewing and posting secrets
– Step 3: Building a sexy secret wall
– Step 4: adding Likes and Comments for Secret
– Step 5: Saving Secrets in the JCR
– Step 6: Let the whole world create new secrets
– Step 7: Finishing the job properly with unit test
Join the eXo tribe by registering for the community and get updates, tutorials, support, and access to the Platform and add-on downloads!
Make the most out of eXo Platform 4
Register to the next webinar and get a complete overview of what you can do with eXo Platform 4. Reserve your seat now!