dimanche 23 septembre 2012

Gwt: requestFactory et spring

Avant la version 2.1, il n'y avait que les RPC pour communiquer avec le serveur en Gwt. Depuis cette version, un nouveau api est disponible, Request Factory.

Cet api permet de n'envoyer que les informations nécessaires au client et au serveur. Il y a économie de bande passante. Au niveau du code, beaucoup de changement sont à prévoir. J'ai passé beaucoup de temps à essayer d'utiliser cet api avec Spring.

La base de donnée h2, spring 3.1 ainsi que gwt 2.4 ont été utilisé pour cet exemple.

L'interface graphique comporte deux boutons, un pour charger toutes les données de la base de donnée et l'autre pour y ajouter un élément.




Le fichier web.xml

<web -app="-app" version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemalocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
 
    <context -param="-param">
        <param -name="-name" />contextConfigLocation
        <param -value="-value" />/WEB-INF/applicationContext.xml
    </context>
    <listener>
        <listener -class="-class">org.springframework.web.context.ContextLoaderListener</listener>
    </listener>
    <servlet>
        <servlet -name="-name">requestFactoryServlet</servlet>
        <servlet -class="-class">com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet>
    </servlet>
    <servlet -mapping="-mapping">
        <servlet -name="-name">requestFactoryServlet</servlet>
        <url -pattern="-pattern">/gwtRequest</url>
    </servlet>
    <welcome -file-list="-file-list">
        <welcome -file="-file">welcomeGWT.html</welcome>
    </welcome>
</web>


Dans le fichier applicationContext.xml, je n'ai que

<context:annotation -config="-config">
<context:component -scan="-scan" base-package="org.calibra">


<bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
          p:location="/WEB-INF/jdbc.properties" />

    <bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource"
      p:driverClassName="${jdbc.driverClassName}"
      p:url="${jdbc.url}"
      p:username="${jdbc.username}"
      p:password="${jdbc.password}" />


Dans le fichier Main.gwt.xml, il faut ajouter

<inherits name="javax.validation.Validation"/>
<inherits name='com.google.web.bindery.requestfactory.RequestFactory'/>

Dans le package Domain

public class Account {
    private Long id;
    private String username;
    private Integer version;

    ....
}

Dans le package server


La classe AccountLocator est nécessaire afin de ne pas mettre ces opérations directement dans la classe Account.

public class AccountLocator extends Locator<Account, Long> {

    @Autowired
    private AccountDAO accountDAO;

    @Override
    public Account create(Class<? extends Account> clazz) {
        return new Account();
    }

    @Override
    public Account find(Class<? extends Account> clazz, Long id) {

        if (accountDAO == null) {
            HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
            ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
            accountDAO =context.getBean(AccountDAO.class);
        }
        return accountDAO.findAccount(id);
    }

    @Override
    public Class<Account> getDomainType() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Long getId(Account domainObject) {
        return domainObject.getId();
    }

    @Override
    public Class<Long> getIdType() {
        return Long.class;
    }

    @Override
    public Object getVersion(Account domainObject) {
        return domainObject.getVersion();
    }


La classe SpringServiceLocator est nécessaire afin d'avoir accès au bean.

public class SpringServiceLocator implements ServiceLocator {
    @Override
    public Object getInstance(Class<?> clazz) {
        HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
        ServletContext servletContext = request.getSession().getServletContext();
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        return context.getBean(clazz);
    }
}



Dans le package server.service

public interface AccountService {
    public Account findAccount(Long id);
    public List<Account> loadAllAccounts();
    public void save(Account account);
    public void delete(Account account);
}


 @Service
public class AccountServiceImpl implements AccountService{
  
    @Autowired
    private AccountDAO accountDAO;

    @Override
    public void save(Account account){
        accountDAO.save(account);
    }

    @Override
    public List<Account> loadAllAccounts() {
        return accountDAO.loadAllAccounts();
    }

    @Override
    public Account findAccount(Long id) {
        return accountDAO.findAccount(id);
    }

    @Override
    public void delete(Account account) {
        accountDAO.delete(account);
    }
}



Dans le package server.dao

@Repository
public class AccountDAOImpl implements AccountDAO {

    protected JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccount(Long id) {
        StringBuffer sb = new StringBuffer();
        sb.append("select id, username, version from account where id = ?");
        List<Account> accountList = jdbcTemplate.query(sb.toString(),
                new Object[]{id}, mapper);

        if (accountList != null && accountList.size() > 0) {
            return accountList.get(0);
        }
        return null;
    }

    @Override
    public List<Account> loadAllAccounts() {

        StringBuffer sb = new StringBuffer();
        sb.append("select id, username, version from account");
        List<Account> accountList = jdbcTemplate.query(sb.toString(), mapper);
        return accountList;
    }

    @Override
    public void save(Account account) {
        if (account.getVersion() == null) {
            account.setVersion(0);
            jdbcTemplate.update("insert into account(username, version) values(?, ?)",
                    account.getUsername(), account.getVersion());
        } else {
            account.setVersion(account.getVersion() + 1);
            jdbcTemplate.update("update account(username, version) set username=?, version=? where id=?",
                    account.getUsername(), account.getVersion(), account.getId());
        }
    }

    @Override
    public void delete(Account account) {
        jdbcTemplate.update("delete from account where id = ?", account.getId());
    }

    @Autowired
    public void setDataSource(DataSource datasource) {
        this.jdbcTemplate = new JdbcTemplate(datasource);
    }
    private static ParameterizedRowMapper<Account> mapper = new ParameterizedRowMapper<Account>() {
        @Override
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
            Account account = new Account();
            account.setId(rs.getLong("id"));
            account.setUsername(rs.getString("username"));
            account.setVersion(rs.getInt("version"));
            return account;
        }
    };
}


public interface AccountDAO {  
    public Account findAccount(Long id);
    public List<Account> loadAllAccounts(); 
    public void save(Account account);
    public void delete(Account account);
}

 

 Dans le package shared


public interface AccountRequestFactory extends RequestFactory {
    AccountRequest accountRequest();
}


@Service(locator = SpringServiceLocator.class, value =AccountService.class)
public interface AccountRequest extends RequestContext {

    Request<AccountProxy> findAccount(Long id);
    Request<List<AccountProxy>> loadAllAccounts();
    Request<Void> save(AccountProxy accountProxy);
    Request<Void> delete(AccountProxy accountProxy);
}


@ProxyFor(value=Account.class, locator = AccountLocator.class)
public interface AccountProxy extends EntityProxy{
    public Long getId();
    public String getUsername();
    public Integer getVersion();
   
    public void setUsername(String userName);
    public void setId(Long id);
    public void setVersion(Integer version);
 }


Dans le package client

public class MainEntryPoint implements IsWidget, EntryPoint {

    private final AccountRequestFactory requestFactory = GWT.create(AccountRequestFactory.class);
    AccountRequest request;
    AccountProxy newAccount;

    @Override
    public Widget asWidget() {

        Button loadAllButton = new Button("Load all Account");
        final ValueListBox<AccountProxy> accountListBox = new ValueListBox<AccountProxy>(new Renderer<AccountProxy>() {
            @Override
            public String render(AccountProxy object) {
                return object.getUsername();
            }

            @Override
            public void render(AccountProxy object, Appendable appendable) throws IOException {
                appendable.append(object.getUsername());
            }
        });

        loadAllButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                request = requestFactory.accountRequest();
                Request<List<AccountProxy>> loadAllAccounts = request.loadAllAccounts();

                loadAllAccounts.fire(new Receiver<List<AccountProxy>>() {
                    @Override
                    public void onSuccess(List<AccountProxy> response) {
                        accountListBox.setAcceptableValues(response);
                        Window.alert("account ajouté");
                    }
                });
            }
        });

        Button addButton = new Button("Add account");
        addButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                request = requestFactory.accountRequest();
                AccountProxy accountProxy = request.create(AccountProxy.class);
                accountProxy.setUsername("joe");

                request.save(accountProxy).fire(new Receiver<Void>() {
                    @Override
                    public void onSuccess(Void response) {
                        Window.alert("account ajouté");
                    }
                });
            }
        });

        VerticalPanel vp = new VerticalPanel();
        vp.setSpacing(10);
        vp.add(loadAllButton);
        vp.add(accountListBox);
        vp.add(addButton);
        return vp;
    }

    @Override
    public void onModuleLoad() {
        requestFactory.initialize(new SimpleEventBus());
        RootPanel.get().add(asWidget());
    }
}


Liste de jar présent dans WEB-INF/lib/




Les sources de cet exemples est disponible ici.
Ces jar ne sont pas inclus dans les sources.