Sunday, December 29, 2013

A Real World Example of GWT 2.x, MySQL, and MVP: Part 3

Part 1: Getting Started
Part 2: Putting the Framework Together
Part 3: Finally Making the Page Content
Part 4: User Authentication 

Finally Making the Page Content


This is a continuation of my previous blog.

This tutorial is a how-to guide to help you build an actual, scalable, website with GWT and a MySQL back-end.  Right now if you try to Run or Debug your project it isn't going to do a lot as there is much to still set up.  Let's begin by starting from the bottom and continue to work our way up and by the end of this article we should have a site that we can launch from Eclipse with a home page, an about page, and getting data from our MySQL database.  W00t!

This page will follow the MVP architecture and is not intended to teach it to you.  In short you will have 2 classes for a single page, a presenter that handles all of the business logic, and a view that handles the layout.  If you want to know more than please first read an MVP tutorial like this.

I will not be using UiBinder feature since, GWT 2.5.1 the current version as of this article, is total crap when working with the MVP architecture.  Seriously it is if MVP and internationalization were an afterthought when Google added it to GWT.  I will create a parallel tutorial page for those using the UiBinder feature.

We will first review the header then work our way on to the different content pages.

Header Presenter

The header presenter is business logic for the header component of our page.  It contains an inner interface called "Display" that assists in communication with the header view.  The constructor has a parameter of the data factory and the header view object.

package com.example.searchengines.client.presenter;

import com.example.searchengines.client.DataFactory;
import com.example.searchengines.shared.User;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;

/**
 * A presenter for the header
 */
public class HeaderPresenter implements Presenter {

    public interface Display {
        HasClickHandlers getHome();
        HasClickHandlers getAbout();
        HasClickHandlers getLogIn();
        HasClickHandlers getLogOut();
        HasClickHandlers getRegister();
        void setData();
        void setData(User user);
        void setSelected();
        Widget asWidget();
    }
    
    private final DataFactory factory;
    private final Display display;
    
    /**
     * A presenter for the header
     * @param factory The data factory
     * @param view The header view
     */
    public HeaderPresenter(DataFactory factory, Display view) {
        this.factory = factory;
        this.display = view;
    }
    
    
    /**
     * Bind events and actions
     */
    public void bind() {
        
        display.getHome().addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                History.newItem("home");
                display.setSelected();
            }            
        });
        
        display.getAbout().addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                History.newItem("about");
                display.setSelected();
            }
        });    
        
        display.getLogIn().addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                //TODO 10: Add login to history
            }
        });
        
        display.getLogOut().addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {    
                //TODO 5: Fire LogOutEvent           
            }
        });
        
        display.getRegister().addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                History.newItem("register");
                display.setSelected();
            }
        });
        
        //TODO 6: Listen for LogInEvents
    }

    
    /**
     * Go forth and build the page
     * @param contents Html content node
     */
    public void go(final HasWidgets contents) {
        bind();
        contents.clear();
        contents.add(display.asWidget());
        display.setSelected();
    }
}

Header View

The header view implements the Display interface of the header presenter, which helps to allow communication between the header and the presenter.  It has 2 setData() methods, one for when the site is accessed anonymously or when a user has logged in.

package com.example.searchengines.client.view;

import com.example.searchengines.client.presenter.HeaderPresenter;
import com.example.searchengines.client.widget.HorizontalDivPanel;
import com.example.searchengines.client.widget.VerticalDivPanel;
import com.example.searchengines.shared.User;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;

/**
 * Header page
 */
public class HeaderView extends View implements HeaderPresenter.Display {
            
    private final Button homeButton = new Button(constants.home());
    private final Button aboutButton = new Button(constants.about());
    private final Button loginButton = new Button(constants.login());
    private final Button logoutButton = new Button(constants.logout());
    private final Button registerButton = new Button(constants.register());
    private final HorizontalDivPanel rightPanel = new HorizontalDivPanel();
    

    /**
     * Header page
     */
    public HeaderView() {
        VerticalDivPanel mainPanel = new VerticalDivPanel();
        mainPanel.addStyleName("mainpanel");
        initWidget(mainPanel);            
        
        //Title
        Label titleLabel = new Label(constants.name());
        titleLabel.setStyleName("titletext");
        mainPanel.add(titleLabel);
        
        //Control Panel
        HorizontalDivPanel controlPanel = new HorizontalDivPanel();
        controlPanel.addStyleName("headerpanel");
        mainPanel.add(controlPanel);
            
            homeButton.setStyleName("headerbutton");
            controlPanel.add(homeButton);

            aboutButton.setStyleName("headerbutton");
            controlPanel.add(aboutButton);
            
            //Right panel
            controlPanel.add(rightPanel);
            controlPanel.setChildStyleName("alignright", controlPanel.getWidgetIndex(rightPanel));
            
        //Show default buttons
        setData();
    }
    
    
    /**
     * Set data for users not logged in
     */
    public void setData() {
        //Clear right panel
        rightPanel.clear();    
        
        //Login button
        registerButton.setStyleName("headerbutton");
        rightPanel.add(registerButton);
        
        Label seperatorLabel = new Label("|");
        seperatorLabel.setStyleName("headerpaneltext");
        rightPanel.add(seperatorLabel);
        
        //Login button
        loginButton.setStyleName("headerbutton");
        rightPanel.add(loginButton);
    }
        
    
    /**
     * Sets data for a user who is logged in
     */
    public void setData(User user) {
        //Clear right panel
        rightPanel.clear();
        
        //Hello user
        Label userLabel = new Label(constants.welcome() + " " + user.getFirstName());
        userLabel.setStyleName("headerpaneltext");
        rightPanel.add(userLabel);
        
        Label seperatorLabel = new Label("|");
        seperatorLabel.setStyleName("headerpaneltext");
        rightPanel.add(seperatorLabel);
            
        logoutButton.setStyleName("headerbutton");
        rightPanel.add(logoutButton);
    }
    
    
    /**
     * Sets a CSS style on the selected button
     */
    public void setSelected() {
        //Clear selected
        homeButton.removeStyleDependentName("selected");
        aboutButton.removeStyleDependentName("selected");
        loginButton.removeStyleDependentName("selected");
        logoutButton.removeStyleDependentName("selected");
        registerButton.removeStyleDependentName("selected");
        
        //Set selected
        String selected = History.getToken();        
        if (selected.equals("home")) {
            homeButton.setStyleDependentName("selected", true);
        }    
        else if (selected.equals("about")) {
            aboutButton.setStyleDependentName("selected", true);
        }
        else if (selected.equals("login")) {
            loginButton.setStyleDependentName("selected", true);
        }
        else if (selected.equals("logout")) {
            logoutButton.setStyleDependentName("selected", true);
        }
        else if (selected.equals("register")) {
            registerButton.setStyleDependentName("selected", true);
        }
    }
    

    /**
     * Home button
     */
    public HasClickHandlers getHome() {
        return this.homeButton;
    }


    /**
     * About button
     */
    public HasClickHandlers getAbout() {
        return this.aboutButton;
    }


    /**
     * Log in button
     */
    public HasClickHandlers getLogIn() {
        return this.loginButton;
    }


    /**
     * Log out button
     */
    public HasClickHandlers getLogOut() {
        return this.logoutButton;
    }


    /**
     * Registration button
     */
    public HasClickHandlers getRegister() {
        return registerButton;
    }
}


Adding the Header to the App Controller

Now that we have our header all defined and ready for prime time we just have to attach it to the HTML header node in the App Controller.  So open AppController.java and find TODO comment number 1:
//TODO 1: Add Header presenter and view
and replace it with an instantiation of the header presenter, passing in the data factory and a new header view:
Presenter presenter = new HeaderPresenter(factory, new HeaderView());
presenter.go(header);

So your final setHeader() method should look like this:
public void setHeader(final HasWidgets header) {
    try {    
        this.header = header;
        Presenter presenter = new HeaderPresenter(factory, new HeaderView());
        presenter.go(header);
    }
    catch (Exception ex) {
        ex.printStackTrace();
    }
}

Home Presenter

package com.example.searchengines.client.presenter;

import java.util.List;
import com.example.searchengines.client.DataFactory;
import com.example.searchengines.shared.SearchEngine;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SingleSelectionModel;

/**
 * A presenter for the home page
 */
public class HomePresenter implements Presenter {

    public interface Display {
        HasClickHandlers getAdd();        
        SingleSelectionModel<SearchEngine> getSelectionModel();
        void setData(List<SearchEngine> rowData);
        void showError(String message);
        Widget asWidget();
    }

    private final DataFactory factory;
    private final Display display;

    
    /**
     * A presenter for the home page
     * @param factory
     * @param view
     */
    public HomePresenter(DataFactory factory, Display view) {
        this.factory = factory;
        this.display = view;
    }

    
    /**
     * Bind events and actions
     */
    public void bind() {
    }
     
    
    /**
     * Go forth and build the page
     */
    public void go(final HasWidgets container) {
        bind();
        container.clear();
        container.add(display.asWidget());
        this.fetchData();
    }
    
    
    /**
     * Fetch page information
     */
    private void fetchData() {
        try {                    
            //TODO 3: Let's use MySQL!
        } catch (Exception e) {
            e.getStackTrace();
        }
    }
}

Home View

The home view will use my custom StarRatingStarRatingCell and StarRatingColumn widgets from my previous blogs where you can add them to your client's "widget" package.

package com.example.searchengines.client.view;

import java.util.List;
import com.example.searchengines.client.presenter.HomePresenter;
import com.example.searchengines.client.widget.HorizontalDivPanel;
import com.example.searchengines.client.widget.StarRating;
import com.example.searchengines.client.widget.StarRatingColumn;
import com.example.searchengines.client.widget.VerticalDivPanel;
import com.example.searchengines.shared.SearchEngine;
import com.google.gwt.cell.client.ClickableTextCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.view.client.DefaultSelectionEventManager;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SingleSelectionModel;

/**
 * Home page
 */
public class HomeView extends View implements HomePresenter.Display {    
    
    private Label errorLabel = new Label();
    private Button addButton = new Button(constants.add());
    private CellTable<SearchEngine> cellTable;
    private SingleSelectionModel<SearchEngine> selectionModel;
    
    /**
     * Home page
     */
    public HomeView() {
        
        //Main panel
        VerticalDivPanel mainPanel = new VerticalDivPanel();
        mainPanel.addStyleName("mainpanel");
        mainPanel.addStyleName("homepage");
        initWidget(mainPanel);

        errorLabel.setStyleName("errorlabel");
        errorLabel.setVisible(false);
        mainPanel.add(errorLabel);
        
        //TODO 7: Add new search engine button
        
        //Welcome
        VerticalDivPanel dataPanel = new VerticalDivPanel();
        dataPanel.addStyleName("datapanel");
        mainPanel.add(dataPanel);

            //Table key provider for sorting
            ProvidesKey<SearchEngine> keyProvider = new ProvidesKey<SearchEngine>() {
                public Object getKey(SearchEngine item) {                  
                    return (item == null) ? null : item.getId();       
                }     
            };                                                 
            
            //Data table
            cellTable = new CellTable<SearchEngine>(keyProvider);
            cellTable.setWidth("100%");    
            mainPanel.add(cellTable);
            
            //Add a selection model to handle user selection.     
            selectionModel = new SingleSelectionModel<SearchEngine>(keyProvider);     
            cellTable.setSelectionModel(selectionModel, DefaultSelectionEventManager.<SearchEngine>createDefaultManager());             
        
                //Create name column.
                Column<SearchEngine, String> nameColumn = new Column<SearchEngine, String>(new ClickableTextCell()) {
                    @Override
                    public String getValue(SearchEngine searchEngine) {
                        return searchEngine.getName();
                    }     
                };
                nameColumn.setFieldUpdater(new FieldUpdater<SearchEngine, String>() {   
                    public void update(int index, SearchEngine searchEngine, String value) {     
                        Window.open(searchEngine.getUrl(),"_blank","");
                    } 
                }); 
                nameColumn.setCellStyleNames("hyperlink");
                cellTable.addColumn(nameColumn, constants.headername());    
                
                //Create url column.
                TextColumn<SearchEngine> urlColumn = new TextColumn<SearchEngine>() {
                    @Override
                    public String getValue(SearchEngine searchEngine) {
                        return searchEngine.getUrl();
                    }     
                }; 
                cellTable.addColumn(urlColumn, constants.headerurl());
                
                //Category column.
                TextColumn<SearchEngine> categoryColumn = new TextColumn<SearchEngine>() {
                    @Override
                    public String getValue(SearchEngine searchEngine) {
                        return searchEngine.getCategory().toString();
                    }     
                }; 
                cellTable.addColumn(categoryColumn, constants.headercategory());
                        
                //Rating column
                StarRatingColumn<SearchEngine> ratingColumn = new StarRatingColumn<SearchEngine>() {
                    @Override    
                    public StarRating getValue(SearchEngine searchEngine) {
                        return new StarRating(searchEngine.getRating(), 10, true);                    
                    }                                  
                };
                cellTable.addColumn(ratingColumn, constants.headerrating());    
                
                //TODO 8: Admin controls
    }
    
    
    /**
     * Add button
     */
    public HasClickHandlers getAdd() {
        return this.addButton;
    }

    
    /**
     * The table selection model
     */
    public SingleSelectionModel<SearchEngine> getSelectionModel() {
        return selectionModel;
    }


    /**
     * Set table data
     * @param data The data to set
     */
    public void setData(List<SearchEngine> data) {
        cellTable.setRowData(data);
    }


    /**
     * Show error message
     */
    public void showError(String message) {
        errorLabel.setText(message);
        errorLabel.setVisible(true);
    }
}

Adding the Home Page to the App Controller

Now that we have our home component all defined we just have to attach it to the HTML contents node.  So open AppController.java and find TODO comment number 2:
//TODO 2: For every page you create set the presenter here
and replace it with an instantiation of the header presenter, passing in the data factory and a new header view:
if (token.getPage().equals("home")) {
    presenter = new HomePresenter(factory, new HomeView());
} 

So your onValueChange() method should look like this:
public void onValueChange(ValueChangeEvent<String> event) {
    Token token = new Token(event.getValue());
        
    if (token.getPage() != null) {
        Presenter presenter = null;            
            
        // For every page you create set the presenter here
        if (token.getPage().equals("home"))    {
            presenter = new HomePresenter(factory, new HomeView());
        }        
            
        //Load presenter
        if (presenter != null) {
            presenter.go(contents);            
        }
    }
}


About Presenter

We will create an about page, which will just be a page with a little text blurb just so we have another page to click around to.
package com.example.searchengines.client.presenter;

import com.example.searchengines.client.DataFactory;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;

/**
 * A presenter for the about page
 */
public class AboutPresenter implements Presenter {

    public interface Display {
        Widget asWidget();
    }

    @SuppressWarnings("unused")
    private final DataFactory factory;
    private final Display display;
    
    /**
     * A presenter for the about page
     * @param factory
     * @param view
     */
    public AboutPresenter(DataFactory factory, Display view) {
        this.factory = factory;
        this.display = view;
    }
    
    /**
     * Bind events and actions
     */
    public void bind() {
    }
      
    /**
     * Go forth and build the page
     */
    public void go(final HasWidgets container) {
        bind();
        container.clear();
        container.add(display.asWidget());
    }
}

About View

Before we can create our about view we have to first add a new string constant to SearchEngineConstants.java in our "client" package.
@DefaultStringValue("This is a website for my mom to help her find things on the web.")
String abouttext();

Now we can use it in our view.
package com.example.searchengines.client.view;

import com.example.searchengines.client.presenter.AboutPresenter;
import com.example.searchengines.client.widget.VerticalDivPanel;
import com.google.gwt.user.client.ui.Label;

/**
 * About page
 */
public class AboutView extends View implements AboutPresenter.Display {    

    /**
     * About page
     */
    public AboutView() {
        
        //Main panel
        VerticalDivPanel mainPanel = new VerticalDivPanel();
        mainPanel.addStyleName("mainpanel");
        mainPanel.addStyleName("aboutpage");
        initWidget(mainPanel);
        
        Label aboutLabel = new Label(constants.abouttext());
        mainPanel.add(aboutLabel);        
    }    
}

Adding the About Page to the App Controller

In the HeaderPresenter.java class you will see we have already added a click handler for the about button to set the history to "about".  This will change the url in the browser from "www.mysite.com/home" to "www.mysite.com/about" and if you remember the App Controller is listening for changes to the history where we can then set what page is called depending on the current history value.  All we have to do is go back to AppController.java to the TODO comment number 2 and add an else if statement to set the presenter to an AboutPresenter.

// For every page you create set the presenter here
if (token.getPage().equals("home")) {
    presenter = new HomePresenter(factory, new HomeView());
}        
else if (token.getPage().equals("about")) {
    presenter = new AboutPresenter(factory, new AboutView());
}

Getting Data From MySQL

Now we need to add a a way for us to retrieve a list of active search engines to fill our table on the home page.  Add the following method to your MySQLConnection.java class in the "server" package:

/**
* Gets a list of search engines
* @return An ArrayList of SearchEngine
* @throws Exception
*/
public List<SearchEngine> getSearchEngines() throws Exception {
    List<SearchEngine> returnObjects = new ArrayList<SearchEngine>();
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet result = null;

    try {
        conn = getConnection();
        pstmt = conn.prepareStatement("SELECT * FROM search_engines WHERE is_active = TRUE ORDER BY rating DESC");
        result = pstmt.executeQuery();
        while (result.next()) {
            SearchEngine searchEngine = new SearchEngine();
            searchEngine.setId(result.getDouble("id"));
            searchEngine.setName(result.getString("name"));
            searchEngine.setUrl(result.getString("url"));
            searchEngine.setCategory(Category.fromInteger(result.getInt("category_id")));
            searchEngine.setRating(result.getInt("rating"));
            searchEngine.setActive(result.getBoolean("is_active"));
            returnObjects.add(searchEngine);
        }
    } catch (SQLException sqle) {
        logger.error("SQL error in getSearchEngines(): " + sqle.getMessage() + "\n" + sqle.getStackTrace().toString());
    } finally {
        result.close();
        pstmt.close();
        conn.close();
    }

    return returnObjects;
}

Add this method to your DBConnection.java class in the client's "services" package:

/**
 * Gets a list of search engines
 * @return An ArrayList of BestRating
 * @throws Exception
 */
public List<SearchEngine> getSearchEngines() throws Exception;

And finally add this method to your DBConnectionAsync.java class in the client's "services" package:

/**
 * Gets a list of search engines
 * @param callback Async return of the SearchEngine List
 * @return An ArrayList of SearchEngine
 * @throws Exception
 */
public void getSearchEngines(AsyncCallback<List<SearchEngine>> callback) throws Exception;

Now that we have a way to query data from our MySQL database and transfer it to our client let's go ahead an hook it up.  Open HomePresenter.java in your client's "presenter" package and find the TODO comment number 3:
//TODO 3: Let's use MySQL!
and replace it with a call to the method we just created through the data factory.
try {                    
    factory.getRpcService().getSearchEngines(new AsyncCallback<List<SearchEngine>>() {
        public void onFailure(Throwable caught) {
            display.showError("Failure getting the search engines: " + caught.getMessage());
        }

        public void onSuccess(List<SearchEngine> result) {
            display.setData(result);
        }
    });
} catch (Exception e) {
    display.showError("Error getting the search engines: " + e.getMessage());
}

So your fetchData() method should look like this:
private void fetchData() {
        try {                    
            factory.getRpcService().getSearchEngines(new AsyncCallback<List<SearchEngine>>() {
                public void onFailure(Throwable caught) {
                    display.showError("Failure getting the search engines: " + caught.getMessage());
                }

                public void onSuccess(List<SearchEngine> result) {
                    display.setData(result);
                }
            });
        } catch (Exception e) {
            display.showError("Error getting the search engines: " + e.getMessage());
        }
    }


Stylin'

Look, I suck at CSS so don't judge me alright.  Place this in your SearchEngines.css file in your project's "war" folder:
/** --- Html --- */
html {height:100%;}
body {height:100%;}
div {display:block;}
table {border-collapse:collapse; border-spacing:0;}

/** --- DivPanels --- */
.vertical {vertical-align: top;    margin: 0px; padding: 0px; width: 100%;}
.vertical > ul {list-style: none; margin: 0px; padding: 0px; width: 100%;}
.vertical > ul > li {display: block; text-decoration: none; margin: 0px; padding:0px 0px; overflow:hidden;}
.horizontal {padding: 0px; margin: 0px;    width: 100%;}
.horizontal > ul {float: left; list-style: none; padding: 0px; margin: 0px; width: 100%;}
.horizontal > ul > li {float: left;    display: block;    text-decoration: none; margin: 0px;    padding: 0px;}

/** --- Main Panels --- */
.alignright {float:right!important;}
.mainpanel {background:#fff9fe; min-width:780px; max-width:1260px; min-height:100%; margin:0px auto; padding:10px}
.errortext {color:red;}

/** -- Header -- */
.headerpanel {height:30px; background:#eeeeee; border:2px solid #cccccc; width:1240px;}
.headerbutton {border:none; margin:0px 2px; padding:6px 15px; color:#black; cursor:pointer; background:transparent;}
.headerbutton:hover {background:#dddddd;}
.headerbutton-selected {background:#fffaa5;}
.headerbutton-selected:hover {background:#fffaa5;}
.headerpaneltext {padding:6px 3px; color:black; background:transparent;}
.titletext {font-size:200%; margin:25px 0px 5px 0px;}

Debug Time!

Holy crap! Quick, right click your project and choose Debug As > Web Application, then in the Development Mode tab right click http://127.0.0.1:8888/SearchEngines.html?gwt.codesvr=127.0.0.1:9997  and choose Open or Open With and then whatever Chrome browser Chrome you Chrome prefer.  Hopefully you will see a page that looks something like this:


Hooray! I'm not an idiot! (At least when it comes to GWT.)  Don't worry if the page seems slow to load, that is just the debugger and will change once you place it on an actual web server.  I'm glad you took the time and stepped through each of my tutorials... or you could have cheated and downloaded the source here: Download Source

Now let's add some authentication!

A Real World Example of GWT 2.x, MySQL, and MVP: Part 2

Part 1: Getting Started
Part 2: Putting the Framework Together
Part 3: Finally Making the Page Content
Part 4: User Authentication 
 

Putting the Framework Together


This is a continuation of my previous blog. You will need to step through Part 1 and also my GWT 2.x and MySQL blog.

This tutorial is a how-to guide to help you build an actual, scalable, website with GWT and a MySQL back-end.  Right now if you try to Run or Debug your project it isn't going to do a lot as there is much to still set up.  Let's begin by starting from the bottom and continue to work our way up and by the end of this article we should have a framework in place where we can then start creating our web pages.


SearchEngines.html

To display our page we should first modify the HTML file in your project's "war" folder.  This HTML file will be the container for the entire site and all we need are nodes for GWT to attach the header, the main content, and a footer if we so desire.  Just a quick note that the div node is using a vertical CSS class from my blog on Fixing GWT's Layout Widgets that we will incorporate in Part 3 of this tutorial.

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="SearchEngines.css">
    <title>Mom's Search Engine Rankings</title>
    <script type="text/javascript" language="javascript" src="searchengines/searchengines.nocache.js"></script>
  </head>
  <body style="background:#b0091c; line-height: 1.5;">
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>
      <div class="vertical mainpanel">
          <ul>
              <li id="header"/>
              <li id="content"/>
              <li id="footer"/>
          </ul>
      </div>
  </body>
</html>

Add Display Widgets

Let's now add the building blocks of our site with the HorizontalDivPanel and VerticalDivPanel from my previous blog, Fixing GWT's Layout Widgets that use CSS for your layout instead of the nasty GWT layout widgets that use HTML tables.  Place the HorizontalDivPanel.java and VerticalDivPanel.java in the client's widget package.

Project UI Constants

In the client package we will create a class that will hold all of the text that will appear in the UI.  No text that appears on the web pages should be hard coded, this rule will be very important when you go to translate your website into another language.  You might scoff and say, "But my website will never be localized!"  Even if the likelihood of your site going global is slim-to-none you should always be following best practices since cutting corners like this is a true sign of a shitty programmer.  Seriously I would look at your project and ask what podunk community college did you get your agriculture degree in before writing your code.

package com.example.searchengines.client;

import com.google.gwt.i18n.client.Constants;

/**
 * Project UI constants
 */
public interface SearchEnginesConstants extends Constants {
   
    @DefaultStringValue("About")
    String about();
    
    @DefaultStringValue("Add")
    String add();    
    
    @DefaultStringValue("Delete")
    String delete();
    
    @DefaultStringValue("Edit")
    String edit();
    
    @DefaultStringValue("Home")
    String home();
    
    @DefaultStringValue("Log In")
    String login();
    
    @DefaultStringValue("Log Out")
    String logout();

    @DefaultStringValue("Mom's Search Engine Ratings")
    String name();
    
    @DefaultStringValue("Register")
    String register();

    @DefaultStringValue("Hello")
    String welcome();
}

Project Configuration Properties

In the client package I like to create a configuration class that will hold some constants for the entire project.  Values like the version number, my email address, the server address (in case I want to do remote debugging), and some other crap we'll look at in another section of this tutorial. We are going to use a Java properties file to hold our values. I know the properties file are only supposed to be used for UI strings and localization, but seriously this is how every Java developer in the world handles their configuration settings and we will be no different. The one downside to this approach is with GWT. To load a properties file on the client-side you use the Constants interface, but the properties file (as far as I can tell) must be in the client package. This means if you want the server-side classes to use the same file you are shit out of luck. You'll have one property file in "src/main/resources" and duplicate file in "src/main/java/com.example.searchengines.client". It sucks I know. However if you use a build system like Maven you can make keeping these files in sync easier by automatically copying the file in the resources folder over the one in the client folder on every build. The benefit to this is to abstract some values from the code for when deploying to different environments such as production, QA, UAT, and your development servers. (Maybe I'll write a blog on that when I have time.)

First let's create a new file in the folder "src/main/resources" named "config.properties".  If you do not have that folder in your project you will want to create it, then right-click the "resources" folder and choose Build Path > Use as source folder. Here is the contents of "config.properties". If you haven't worked with Java property files before it is a simple text file with name value pairs.

mysql.host=localhost

mail.sender=me@fake.com

version=1.0.0
Now copy that file to the package "com.example.searchengines.client" with the name "Configuration.properties". Great, now we have our properties file in the 2 places where the client and the server can get to them. All that is left is creating a class to read the properties file so we can get to these values in our code. GWT has it's own mechanism (of course it does) to read properties files on the client-side. In your "client" package create a "Configuration" class with a properties for each of the values in the Configuration.properties file. You map each property to the properties file with the Key annotation.

Configuration.java
package com.example.searchengines.client;

import com.google.gwt.i18n.client.Constants;

public interface Configuration extends Constants {

    @Meaning("MySql hostname")
    @Key("mysql.host")
    @DefaultStringValue("localhost")
    String getMySqlHost();
    
    @Meaning("Email address of sender")
    @Key("mail.sender")
    String getMailSender();    
    
    @Meaning("Project version")
    @Key("version")
    String getVersion();
}

And finally create a class in the "server" package named "Configuration".

Configuration.java
package com.example.searchengines.server;

import java.util.ResourceBundle;

public class Configuration {

    private final String mysqlHost;
    private final String mailSender;
    private final String version;
    
    public Configuration() {
        ResourceBundle properties = ResourceBundle.getBundle("config");
        mysqlHost = properties.getString("mysql.host");
        mailSender = properties.getString("mail.sender");
        version = properties.getString("version");
    }

    public String getMysqlHost() {
        return mysqlHost;
    }

    public String getMailSender() {
        return mailSender;
    }

    public String getVersion() {
        return version;
    }
}


Presenter Interface

In the client's presenter package we will add a class called "Presenter.java" that will serve as our base class for all presenter classes.  It's pretty simple yet very important:

package com.rappa.lapizza.client.presenter;

import com.google.gwt.user.client.ui.HasWidgets;

public abstract interface Presenter {
    public abstract void go(final HasWidgets container);
}

View Base Class

In the client's view package we will add a class called "View.java" that will serve as our base class for all view classes and allow us easy access to the project's UI constant values.  Again pretty simple:

package com.example.searchengines.view;

import com.example.searchengines.client.SearchEnginesConstants;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

/**
 * View base class
 */
public class View extends Composite {

    SearchEnginesConstants constants = GWT.create(SearchEnginesConstants.class);
    
    /**
     * View base class
     */
    public View() {
    }
    
        
    /**
     * Gets the widget
     */
    public Widget asWidget() {
        return this;
    }
}

MySQLConnection

Hey have you read through my blog GWT 2.x and MySQL? You will need to in order to get a MySQL connection to work. Trust me don't skip this step. Here is what our MySQLConnection class will look like for our project. Oh look there is the Configuration class we created earlier! Note that we are getting the MySQL host name from the value in the properties file.

package com.example.searchengines.server;

public class MySQLConnection extends RemoteServiceServlet implements DBConnection {

    private static final long serialVersionUID = 1L;
    private Configuration configuration = new Configuration();
    private String url = "jdbc:mysql://" + configuration.getMysqlHost() + ":3306/moms_search_engines";
    private String user = {your user who is not root}
    private String pass = {your password} 
    
    /**
     * Connection to mysql database
     */
    public MySQLConnection() {
    }

    /**
     * Gets the connection
     *
     * @return Connection state
     * @throws Exception
     */
    private Connection getConnection() throws Exception {
        Properties props = new Properties();
        props.setProperty("user", user);
        props.setProperty("password", pass);
        props.setProperty("zeroDateTimeBehavior", "convertToNull");
        Class.forName("com.mysql.jdbc.Driver").newInstance();
        Connection conn = DriverManager.getConnection(url, props);
        return conn;
    }
}

Data Factory

A data factory is a class that does nothing but contain pointers to other repositories.  We will create the data factory once in the client package, then pass it to every presenter so every page will have access to the same MySQL connection, the event bus to send notifications to individual presenters listening for events, and the currently logged in user.  It's not necessary that you use a data factory in your project, but I like to since it's easier passing just the factory to every presenter than n number of parameters that may increase as your project grows (causing rewriting a lot of code.) 

package com.example.searchengines.client;

import com.example.searchengines.service.DBConnectionAsync;
import com.example.searchengines.shared.User;
import com.google.gwt.event.shared.SimpleEventBus;

/**
 * Data factory to encapsulate a few things
 *
 */
public class DataFactory {
    private final DBConnectionAsync rpcService;
    private final SimpleEventBus eventBus;
    private final Configuration configuration;
    private User user;
    
    /**
     * Data factory to encapsulate a few things
     * @param rpcService Remote procedure call service
     * @param eventBus Event bus
     * @param configuration Project configuration
     * @param user User
     */
    public DataFactory(DBConnectionAsync rpcService, SimpleEventBus eventBus, Configuration configuration, User user) {
        this.rpcService = rpcService;
        this.eventBus = eventBus;
        this.configuration = configuration;
        this.user = user;                
    }

    /**
     * The MySQL connection
     * @return the rpcService
     */
    public DBConnectionAsync getRpcService() {
        return rpcService;
    }

    /**
     * The Event Bus
     * @return the eventBus
     */
    public SimpleEventBus getEventBus() {
        return eventBus;
    }

    /**
     * Project configuration
     * @return the configuration
     */
    public Configuration getConfiguration() {
        return configuration;
    }

    /**
     * Information on the currently logged in user
     * @param user the user to set
     */
    public void setUser(User user) {
        this.user = user;
    }

    /**
     * Information on the currently logged in user
     * @return the user
     */
    public User getUser() {
        return user;
    }
}

App Controller

Okay now we're really getting into the meat and potatoes of the MVP architecture!  The App Controller determines what page to display to the user by reading the URL and then instantiating the corresponding presenter and view classes as well as keeping a history of what URLs the user has accessed.  For example if the URL was "www.example.com/home" the App Controller sees the user is requesting the "home" page and will create a new home view and presenter object and attach it to the "content" element in our SearchEngines.html page.  My App Controller also contains a subclass that can read multiple tokens off of the URL.  For example we can call a presenter and pass some information that we want to store in the browser history, such as if the URL was "www.example.com/home?id=42" the home presenter knows the id value is 42.  If you still can't get your head around this go to www.amazon.com and start browsing the site and using filters like only show 4 star books under $100.  You might notice you are accessing the same page but some filter value is added as a token on the URL, and that is how you can call the same page but still have a browser's forward and back buttons still functional.

package com.example.searchengines.client;

import java.util.HashMap;
import com.example.searchengines.presenter.Presenter;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.HasWidgets;


/**
 * The App Controller
 */
public class AppController implements Presenter, ValueChangeHandler<String> {
    private final DataFactory factory;
    private HasWidgets header;     //<li id="header">
    private HasWidgets contents;   //<li id="contents">

    /**
     * The App Controller
     * @param rpcService
     * @param eventBus
     */
    public AppController(DataFactory factory) {
        this.factory = factory;
        bind();
    }


    /**
     * Bind any listeners
     */
    private void bind() {
        History.addValueChangeHandler(this);

        //TODO 4: Listen for LogOutEvent 
    }
    

    /**
     * Attach view to header
     */
    public void setHeader(final HasWidgets header) {
        try { 
            this.header = header;
            //TODO 1: Add Header presenter and view
        }
        catch (Exception ex) {
     ex.printStackTrace();
        }
    }
    

    /**
     * Attaches view to container
     */
    public void go(final HasWidgets contents) {
        try {    
            //Set the contents
            this.contents = contents;

            //If there is no token then set the token to home otherwise fire a history onValueChange event
            if ("".equals(History.getToken())) {
                History.newItem("home");
            } else {
                History.fireCurrentHistoryState();
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }       
    }
    

    /**
     * History and token management
     */
    public void onValueChange(ValueChangeEvent<String> event) {
        //Get the token from the URL
        Token token = new Token(event.getValue());
        
        if (token.getPage() != null) {
            Presenter presenter = null;            
                        
            //TODO 2: For every page you create set the presenter here
            
            //Load presenter
            if (presenter != null) {
                presenter.go(contents);            
            }
        }
    }
    
    
    
    /**
     * Url Token
     * @author Austin_Rappa
     *
     */
    class Token {
    
        HashMap<String, String> params = new HashMap<String, String>();
        private String page = null;
        private double id = 0d;
        
        
        public Token(String token) {
            this.page = this.parseHistoryToken(token);
        }
        
        
        /**
         * parse the historyToken
         * like domaint.tld#anchor?[var=1&var3=2&var3=3] 
         * @param historyToken anchor tag
         */
        private String parseHistoryToken(String historyToken) {
            
            if (historyToken == null) {
                return "";
            }
            
            //get parameters from history token
            if (historyToken.contains("?")) {
                HashMap<String, String> params = getHistoryTokenParameters(historyToken);

                //use the parameters
                setParams(params);

                //get just the history token / anchor tag , not with paramenters
                historyToken = getHistoryToken(historyToken);
            } 
        
            return historyToken;
        }
        
        /**
         * get historyToken parameters
         * like domaint.tld#anchor?[var=1&var3=2&var3=3]
         * @param historyToken anchor tag
         * @return hashmap of the parameters
         */
        private HashMap<String, String> getHistoryTokenParameters(String historyToken) {
        
            //skip if there is no question mark
            if (!historyToken.contains("?")) {
                return null;
            }
            
            // ? position
            int questionMarkIndex = historyToken.indexOf("?") + 1;
            
            //get the sub string of parameters var=1&var2=2&var3=3...
            String[] arStr = historyToken.substring(questionMarkIndex, historyToken.length()).split("&");
            HashMap<String, String>  params = new HashMap<String, String> ();
            for (int i = 0; i < arStr.length; i++) {
                String[] substr = arStr[i].split("=");
                if (substr.length == 2) {
                    params.put(substr[0], substr[1]);
                }
            }

            return params;
        }
        
        /**
         * get historyToken by itself
         * 
         * like domain.tld#[historyToken]?params=1
         *  
         * @param historyToken
         * @return
         */
        private String getHistoryToken(String historyToken) {
            
            //skip if there is no question mark
            if (!historyToken.contains("?")) {
                return "";
            }

            //get just the historyToken/anchor tag
            String[] arStr = historyToken.split("\\?");
            historyToken = arStr[0];
        
            return historyToken;
        }

        /**
         * are there params in historyToken
         * 
         * @return
         */
        @SuppressWarnings("unused")
        private boolean isParamsInHistoryToken() {
            String s = History.getToken();
            
            if (s.contains("?")) {
                return true;
            } else {
                return false;
            }
        }

        /**
         * use the parameters
         * @param params
         */
        private void setParams(HashMap<String, String>  params) {
            
            if (params == null) {
                return;
            }
                
            this.params = params;
            
            if (this.params.get("id") != null) {
                this.id = Double.parseDouble(this.params.get("id"));
            }            
        }
        

        /**
         * @return the page
         */
        public String getPage() {
            return this.page;
        }


        /**
         * @return the id
         */
        public double getId() {
            return this.id;
        }
    
        
        /**
         * @return the parameters
         */
        public HashMap<String, String> getParameters() {
            return this.params;
        }    
    }
}

Entry Point Class

We're in the home stretch!  In the entry point class SearchEngines.java is where we do all of our instantiation of our objects that we pass to the Data Factory such as the MySQL service and the event bus, then pass the Data Factory to the App Controller, give the App Controller what HTML nodes to attach itself to so we can insert our header and contents, and finally tell the App Controller to build the page.  By default that will be the home page unless a user has typed in a valid URL or bookmarked a page.

package com.example.searchengines.client;

import com.example.searchengines.service.DBConnection;
import com.example.searchengines.service.DBConnectionAsync;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.RootPanel;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class SearchEngines implements EntryPoint {

    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {    
        //Connect to MySQL
        DBConnectionAsync rpcService = (DBConnectionAsync) GWT.create(DBConnection.class);
        ServiceDefTarget target = (ServiceDefTarget) rpcService;
        target.setServiceEntryPoint(GWT.getModuleBaseURL() + "MySQLConnection");
            
        //Event bus
        SimpleEventBus eventBus = new SimpleEventBus();

        //Configuration
        Configuration configuration = GWT.create(Configuration.class);
                    
        //Factory
        final DataFactory factory = new DataFactory(rpcService, eventBus, configuration, null);
        
        //AppController
        final AppController appViewer = new AppController(factory);
        appViewer.setHeader(RootPanel.get("header"));
        appViewer.go(RootPanel.get("content"));
    }
}

This seems like a good point to break.  Join us next time for another exiting adventure with Part 3: Finally Making the Page Content!

A Real World Example of GWT 2.x, MySQL, and MVP: Part 1

Part 1: Getting Started
Part 2: Putting the Framework Together
Part 3: Finally Making the Page Content
Part 4: User Authentication

Getting Started


Let's drop all pretenses and games.  You're done playing around, you are here to make an actual website and all you can find is little dinky tutorials that get you halfway there.  Well if you got the time then I got the rhyme... or more specifically a step-by-step guide to help you put all of the pieces together.

This is a continuation of my previous blogs, GWT 2.x and MySQL and Doing More with GWT 2.x and MySQL.  If you haven't read through, fiddled around, and fully understood these pages yet (including the humiliating typos and grammatical errors) then please go back and take your time as the rest of us will wait for you.  I also assume you have some passing familiarity with the MVP framework, and if not please read GWT MVP Architecture Tutorial otherwise you'll be more lost than that time when your momma got lost and they had to use all four sides of the milk carton because she was so fat. Boo-yah!


Planning Your Project

The most important phase of any development cycle is the planning phase.  The more time you spend planning the less time you will spend in the later coding and maintenance phases.  We will start out by asking what do we want to solve?  Who are our customers?  What do our customers want?  Are Reese's Peanut Butter Cups are better than York Peppermint Patties?  Is a website created with GWT the correct tool to solve this problem or provide this service?

Let's say my mom isn't very computer knowledgeable and wants to know the best way to search for something.  Ah ha!  A service or need must be filled by the market!!!  (Forget that thisthis, or this exists!)  For our example I will create a simple website that will rank and link to the different search engines out there. And because we will use the MVP framework for our project will be able to scale the website later on as it grows and adds more functionality.  So by very carefully asking questions that do not "lead" my theoretical mom to a conclusion I ask her things like, "What would this site look like to you?", "What sort of things would it do?", or "Why isn't dinner made yet?"  And from my customer interview(s) about the website functionality we come to a consensus:
  • The layout will have a header for home, about, and login/logout buttons.
  • The name and a link to the website
  • Categories for the type of search engine
  • A ranking value for each site
  • I'm her favorite son
  • A login page for users
  • A registration page for new uers
  • An edit search engine page for administrators 
  • Remove a search engine from the list for administrators
  • Add new search engine page
Now that we have gathered the wants and needs of my customer(s) we now create a basic mockups for every page of my entire website.  Using a program like Balsamiq Mockups we can create an idea of what the home page might look like and use as a reference when we go to build the page.


Finally I take all the mockups to my mom and ask questions about each individual page that again don't lead her to what I want her to say but that allow her to come to her own conclusions.  So I ask her things like, "What do you see when you look at this page?", "What do you expect to happen when you click this button or link?", "And seriously why the fuck is dinner not made yet?"

Getting Your Project Started

In Eclipse I've created a new GWT Project with the name "SearchEngines" and a the package name "com.example.searchengines". The first thing I have done is remove the starter classes "GreetingService", "GreetingServiceAsync", "GreetingServiceImpl", and "FieldVerifyer", as well as removing any servlet reference to them in the web.xml file.

Following the MVP framework I am going to create the following packages in my project's "src" folder:

com.example.searchengines.client.event
The client's event package will contain classes that will handle our custom events

com.example.searchengines.client.presenter
The client's presenter package will have our presenter classes which hold the business logic of the page and works with a companion view class.

com.example.searchengines.client.service
The client's service package is my own personal preference of placing the services in their own subfolder since larger projects can have many services and clutter the client package.

com.example.searchengines.client.view
The client's view package contains the view classes which hold the HTML layout of the page and works with a companion presenter class.

com.example.searchengines.client.widget
A client's widget package is my own personal preference of placing all of the custom widgets you will collect on the web (like a super-cool star rating widget) or create on your own.

The final steps to setting up the project is to very carefully follow my tutorial on GWT 2.x and MySQL which will give us the ability to connect to and query a MySQL database.  The only thing to note is the interfaces DBConnection and DBConnectionAsync should be in the "com.example.searchengines.client.service" package and not the "com.example.searchengines.client" package as described in the tutorial.  When you are finished following the tutorial your project should look like this:


Setting Up Your Database

Now that our project is set up let's add a MySQL database it can connect to.  First you must download and install a MySQL server on your computer from this site MySQL Community Edition Downloads and I recommend also downloading MySQL Workbench to manage and browse your database which you can get here Download MySQL Workbench.  Eventually when you publish your site to your webserver you will also need to install MySQL there as well.

Open MySQL Workbench and you should see "Local Instance of MySQL" in the "SQL Development" section of the home screen which will open a connection to start querying your local MySQL database.  Once the new tab for your local instance of MySQL opens create your new schema, for our example we will name it "moms_search_engines" which will now appear in the "Object Browser".  If your new schema is not bold then either double-click it or right-click it and choose "Set as Default Schema".  Now expand it and create some tables that will hold your website's information.  If you are developing your own schema you have to think very carefully about this before you begin and I suggest reading as many books and articles about database normalization, best practices, and what to avoid.  I cannot stress this enough because once you go down the path of building your schema and once you roll it out to into production it becomes the biggest hassle to re-factor it.  For our example you can use the SQL commands below to create 3 tables just to get us started.

You can step through the SQL manually or download a SQL script here. Sure go ahead and download it, but it won't give you the satisfaction of creating something by yourself.

Table 1: categories
First we will start with the categories table, which is a list of the types of search engines.
CREATE TABLE `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique identifier',
  `name` varchar(45) DEFAULT NULL COMMENT 'Category name',
  `description` varchar(255) DEFAULT NULL COMMENT 'Category description',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='Search engine categories';

Table 2: search_engines
The search_engines table will hold our list of search engines as well as a url link to their site, a category for filtering, a rank value, and if they are active.
CREATE TABLE `search_engines` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique identifier',
  `name` varchar(45) DEFAULT NULL COMMENT 'Search provider name',
  `url` varchar(255) DEFAULT NULL COMMENT 'Search provider url',
  `category_id` int(11) DEFAULT '0' COMMENT 'Search engine category',
  `rating` int(2) DEFAULT '0' COMMENT 'Rating value from 1-10',
  `is_active` tinyint(1) DEFAULT '1' COMMENT 'Is the search provider active?',
  PRIMARY KEY (`id`),
  KEY `fk_search_engines_category` (`category_id`),
  CONSTRAINT `fk_search_engines_category` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB COMMENT='An internet search provider';

Now that we have our tables it is good to get some sample data to get us started.  Nothing major but enough to give us a good idea of how our pages look once we start building running them.  For our example you can use some sample data like below:
INSERT INTO `categories` VALUES 
  (1,'web','Web searches'),
  (2,'metasearch','Metasearch'),
  (3,'food','Foods or recipes'),
  (4,'job','Employment openings'),
  (5,'legal','Case law search'),
  (6,'medical','Medical paper and news');

INSERT INTO `search_engines` VALUES 
  (1,'Google','http://www.google.com',1,9,TRUE),
  (2,'Bing','http://www.bing.com',1,5,TRUE),
  (3,'DuckDuckGo','http://www.duckduckgo.com',1,8,TRUE),
  (4,'AltaVista','http://www.altavista.com',1,4,FALSE),
  (5,'Yahoo!','http://search.yahoo.com',1,6,TRUE),
  (6,'Excite','http://msxml.excite.com',2,7,TRUE),
  (7,'Kayak','http://www.kayak.com',2,9,TRUE),
  (8,'RecipeBridge','http://www.recipebridge.com',3,7,TRUE),
  (9,'Yummly','http://yummly.com',3,6,TRUE),
  (10,'CareerBuilder','http://www.careerbuilder.com',4,5,TRUE),
  (11,'Yahoo! HotJobs','http://www.hotjobs.com',4,5,TRUE),
  (12,'Monster','http://www.monster.com',4,4,TRUE),
  (13,'LexisNexis','http://www.lexisnexis.com',5,9,TRUE),
  (14,'Google Scholar','http://scholar.google.com',5,7,TRUE),
  (15,'WebMD','http://www.webmd.com',6,9,TRUE),
  (16,'Quertle','http://www.quertle.info',6,6,TRUE);

If you read my previous blogs on GWT 2.x and MySQL (and you should have at this point) you should know by now that we are going to create a class in the client's shared package for each table we create, and those classes will have properties to match the table columns.  So the table "search_engines" will have a class called "SearchEngine" and have properties for the id, name, url, category_id, rating, and is_active.

SearchEngine.java

package com.example.searchengines.shared;

import com.google.gwt.user.client.rpc.IsSerializable;

/**
* An internet search provider
*/
public class SearchEngine implements IsSerializable {

    private double id = 0.0d;
    private String name = "";
    private String url = "";
    private Category category = null;
    private int rating = 0;
    private boolean isActive = true;
    

    /**
     * An internet search provider
     */
    public SearchEngine() {
    }


    /**
    * Unique identifier
    * @param id the id to set
    */
    public void setId(double id) {
        this.id = id;
    }


    /**
    * Unique identifier
    * @return the id
    */
    public double getId() {
        return id;
    }


    /**
     * Search provider name
     * @return the name
     */
    public String getName() {
        return name;
    }


    /**
     * Search provider name
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }


    /**
     * Search provider url
     * @return the url
     */
    public String getUrl() {
        return url;
    }


    /**
     * Search provider url
     * @param url the url to set
     */
    public void setUrl(String url) {
        this.url = url;
    }


    /**
     * Search engine category
     * @return the category
     */
    public Category getCategory() {
        return category;
    }


    /**
     * Search engine category
     * @param category the category to set
     */
    public void setCategory(Category category) {
        this.category = category;
    }


    /**
     * Rating value from 1-10
     * @return the rating
     */
    public int getRating() {
        return rating;
    }


    /**
     * Rating value from 1-10
     * @param rating the rating to set
     */
    public void setRating(int rating) {
        this.rating = rating;
    }


    /**
     * Is the search provider active?
     * @return the isActive
     */
    public boolean isActive() {
        return isActive;
    }


    /**
     * Is the search provider active?
     * @param isActive the isActive to set
     */
    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }
}

For the categories, since they are far more static than the other tables in your database, I sometimes find it easier to make it an enum.  The only challenge to this is, unlike the .NET Framework or later versions of Java, enums do not have values and you have to add some methods to do some conversions.  I did one the hard way and another the easy way and it's up to you how you want to approach the value conversion depending on how big your enum is.  Also in order to use the enum in GWT in both your client, service, and server, you have to implement IsSerializable.

Category.java

package com.example.searchengines.shared;

import com.google.gwt.user.client.rpc.IsSerializable;


/**
* Search category
*/
public enum Category implements IsSerializable {
    /**
     * Web searches
     */
    web,
    /**
     * Meta-search
     */
    metasearch,
    /**
     * Foods or recipes
     */
    food,
    /**
     * Employment openings
     */
    job,
    /**
     * Case law search
     */
    legal,
    /**
     * Medical paper and news
     */
    medical;
    
    
    
    /**
     * Return Category from integer
     * @param category Search category
     * @return
     */
    public static Category fromInteger(int category) {
        switch(category) {
            case 1:
                return web;
            case 2:
                return metasearch;
            case 3:
                return food;
            case 4:
                return job;
            case 5:
                return legal;
            case 6:
                return medical;   
            default:
                return null;
        }
    }
    
    
    /**
     * Return Category to integer
     * @param category Search category
     * @return
     */
    public static int toInteger(Category category) {
        int index = 0;
        Category[] categories = Category.values();
        int i = index;
        while (i < categories.length && index == 0) {
            if (categories[i].equals(category))
                index = i;
            i++;
        }
        categories = null;
        return index;
    }
}

One thing you might notice is I've commented everything and that's because as a developer that's part of your damn job! It make take a few extra minutes of your day but do you honestly think that 2 years from now when you look at this code that you're going to remember any of this shit?  Or if you are lucky enough to build a site that matures and you get to hand it off to some code monkey shmuck who will maintain it while you are off building your next super-cool website, you damn well better have comments because it will make you look good and will help the monkey in his job.  So pay it forward and comment your shit.

Adding database users


You wouldn't want your GWT MySQL connection to your database as root, that's just not smart stuff man.  I will create 2 uers, both named "mom", one for the host "%" and "localhost", and granted "SELECT, INSERT, UPDATE, DELETE" on the schema, "moms_search_engines". Give the user "mom" a good strong password, you wouldn't want your mom to be hacked now would you.

CREATE USER 'mom'@'%' IDENTIFIED BY 'mom';
GRANT SELECT,INSERT,UPDATE,DELETE ON moms_search_engines TO 'mom'@'%';

CREATE USER 'mom'@'localhost' IDENTIFIED BY 'mom';
GRANT SELECT,INSERT,UPDATE,DELETE ON moms_search_engines TO 'mom'@'localhost';

FLUSH PRIVILEGES;

And finally update your MySQLConnection class with your new "mom" user and her super-strong password you decided on.

package com.example.searchengines.server;

public class MySQLConnection implements DBConnection {

    private String _user = "mom";
    private String _pass = "mom";


Okay cool we're ready for the next part:
Part 2: Putting the Framework Together