Wednesday, March 30, 2011

We are going to make the Roo-generated ApplicationScaffold.html host page accessible only by admin users and create a second host page visible to all users. While regular users will only see the new host page, admin users will be able to switch back and forth. We are going to implement this functionality without creating a new GWT module and leave this topic to be discussed in my next post.

Before We Start

I have updated the previous post with a fix that needs to be applied to the Roo-generated code before proceeding to the next section. For more information on this fix, please follow this link.

Before jumping into the customization of our Pizza Shop project, lets go through some prerequisite facts that we need to know.

Google App Engine (GAE) Login Functionality

The first screen we are presented with when we launch our Pizza Shop Web application consists of a mock GAE login form. This mock login form allows us to test our application's integration with the GAE Users API and its login functionality without having to deploy our application. There is no actual authentication taking place in this mock login screen. Anything entered will be accepted. The "Sign in as Administrator" checkbox allows us to test how our application handles users with administrative privileges.

GWT Wizards

The Wizards that GWT has made available for us will come in handy in this post as well as the next one. In order to access these wizards, select the "New" submenu's "Other..." item from the project's context menu.

Scroll down to the "Google Web Toolkit" folder to view all GWT wizards available.

We will be working with "HTML Page" and "UiBinder" wizards in this post.

Servlet Filter for Authentication

Our Roo-generated scaffold application uses the ~.server.gae.GaeAuthFilterservlet filter to grant access only to those users who have logged in to the application.

We are now ready to define how our Web application will behave after we have implemented our customizations. We are targeting the following logical flow in our Web application:

A user logs in to the Web application

The user is presented with a new custom screen

If the user has GAE administrative privileges, then a link to the original scaffold screen is displayed. This link will not be visible for regular users without admin privileges.

Admin users who follow this link will be taken to a new host page that displays the original scaffold screen.

The scaffold screen will contain a link to the entry screen. Admin users will return to the new screen when this link is clicked.

In short, we will be using the Roo-generated scaffold interface as an administration console that is only available to users with GAE administrative privileges and creating a new screen for regular users.

Implementation

We will start our customization by creating a new GWT host page for our administration console.

Create admin/console.html host page using "HTML Page" GWT wizard.

After adding a viewportmeta tag, editing the title, and adding a span tag with a message stating that the console is being loaded, we will end up with the following.

GaeAdminFilter behaves exactly the same way as GaeAuthFilter, except that it requires users to have admin privileges in addition to being logged in. It also sends a different error code in the case that these requirements are not met.

We have created GaeAdminFilter, but it won't be used unless we add it to our deployment descriptor. So, lets insert the filter declaration and filter mapping for GaeAdminFilter into our web.xml file.

Without this servlet mapping configuration, we would receive "HTTP 404 - Not Found" error when attempting to make a GWT request from our console.html file. Note that all mapping elements, including this servlet mapping element, have to be placed after the corresponding declaration, which is the requestFactory servlet declaration in this case.

To avoid a runtime exception, we need to handle the new SC_FORBIDDEN error code returned by GaeAdminFilter in the case of request denial. This will be done by adding the following block to handle the new error code in createRequestCallback() method of ~.client.scaffold.gae.GaeAuthRequestTransport.

At this point, our new HTML host page is only accessible by admin users but displays the same content as the original host page. You should be able to run your application and verify that admin users can view both host pages, whereas normal users will be redirected to the login screen when attempting to access console.html.

The next task on our to-do list is to change the content that is displayed by each host page. We want the new console.html to display the original scaffold application, which we intend to use as an administration console, and the ApplicationScaffold.html to display new content.

Lets start by creating a new ~.client.custom.ui.CustomView using UiBinder GWT wizard, which will create a CustomView.ui.xml and a CustomView.java file. CustomView.ui.xml will be a copy of ScaffoldDesktopShell.ui.xml without the entity list and entity details elements and their corresponding style declarations.

We now have to return to our console.html host page to find a way to differentiate it from ApplicationScaffold.html. The span tag for the "loading" message is a perfect candidate for this. Setting the id attribute of the span tag to something that differs from the one in ApplicationScaffold.html will allow us to differentiate the two host pages.

<span id='loadingConsole' style="...">
Loading Admin Console…
</span>

We will update ~client.scaffold.ScaffoldDesktopApp's run() method to check if the host page contains an element with id attribute equal to loading or loadingConsole. This will allow us to determine which host page we are dealing with.

Notice that the LoginWidget initialization by GaeLoginWidgetDriver has moved from the init() method to the run() method as the instance of LoginWidget to be initialized depends on the host page being accessed.

At this point, our new HTML host pages are displaying different content. The original scaffold view has become our administration console, accessible only by admin users through admin/console.html, and our new view is accessible by all registered users through ApplicationScaffold.html.

Now, lets link the two pages to each other by adding new Anchor widgets to both host pages. We want a link to the administration console that is only visible to users with administrative privileges on CustomView and a link that will return admin users back to the CustomView on the administration console (ScaffoldDesktopShell).

We will start by creating a new ~.client.scaffold.ui.ReturnLinkWidget using the UiBinder GWT wizard. ReturnLinkWidget will be simple and contain only one Anchor widget styled the same way as LoginWidget for consistency.

Having accessed GWT's Window module, we are going to have to inherit it in our module definition (ApplicationScaffold.gwt.xml) file by inserting the following line.

<inherits name="com.google.gwt.user.Window"/>

Now that we have created ReturnLinkWidget, we can add it to our administration console view, ~.client.scaffold.ScaffoldDesktopShell. Adding the ReturnLinkWidget and assigning it a custom style for placement will give us the following ScaffoldDesktopShell.ui.xml.

One last modification we need to make before ReturnLinkWidget is fully operational is adding a call to ReturnLinkWidgetDriver's setWidget() method for the case when the admin/console.html host page is being accessed in ~.client.scaffold.ScaffoldDesktopApp's run() method.

You should now be able to see this new link by accessing admin/console.html as an admin user. Clicking the link should take you back to the CustomView.

We need to create one last widget that links admin users to the administration console before we can start selling pizzas online; however, this task is not going to be as easy as ReturnLinkWidget, because we have the requirement of making the link visible only for admin users. In order to accomplish this, we need to know if users have admin privileges on the client side, which means that we will be creating a new service request method.

Lets get started right away by updating ~.shared.gae.GaeUserServiceRequest with our service method declaration.

Time has come to add ConsoleLinkWidget to the new ~.client.custom.ui.CustomView. Adding the ConsoleLinkWidget and assigning it a custom style for placement will give us the following CustomView.ui.xml.

We have achieved all our objectives at this point. Only admin users are able to see a link that takes them to the admin console at this point. They are able to follow this link to the admin console and follow the return link back to the main view.

Source Code

I have zipped up and uploaded the source directory for the Pizza Shop project. The zip file includes all changes made in this post and can be downloaded via this link.

Uploaded To App Engine

I have also deployed the current state of the project to App Engine and it can be accessed via http://1.pizzashopexample.appspot.com. IE9 users might have problems launching the application.

Deploying to App Engine is very easy. Sign up to App Engine, register an application, and enter application ID in appengine-web.xml prior to deploying. Then select "Google > Deploy to App Engine" from project's context menu, enter Google username and password, and you are done!

In The Next Post

We will be creating a new GWT module and linking two modules to each other.

11 comments:

Hi, really great work you are doing! I'm trying to modify the Pizza entity adding an image field (Byte[] image) but something is going wrong over the generated code.Am I doing something wrong? Or just am I trying to do something still not supported in roo-gwt project?

Hi, thanks for this blog. I am also trying to customize my roo gwt project. Using 'new GaeLoginWidgetDriver(requestFactory)...' results in a compilation error, because the constructor of GaeLoginWidgetDriver only accepts objects of type 'MakesGaeRequests', but you use 'ApplicationRequestFactory' instead. What can I do?

My sources say that ApplicationRequestFactory does not extend MakesGaeRequests, but ScaffoldRequestFactory which extends RequestFactory. Maybe I am using old versions (roo version 1.1.1 and GWT 2.1.1). I´m gonna try latest version...

I´ve downloaded the latest roo version 1.1.2, but I did not get my problem to fix. I can not access to your source link because of firewall settings at my office. So I will compare sources when I´m back home tonight. Anyway thanks for your reply! :)

the application at pizzashopexample.appspot.com is somehow taking my login data from the necessary google login, so it is not possible to test the "admin" part. after clicking the "sign out" link, i'm getting fully logged out from my google account (:

Yes, you would need to have administrator privileges to the deployed GAE project to be able to view the admin console link or the console itself. To test the admin functionality, you'll need to deploy your own copy to App Engine.