Création d’un projet Spring + hibernate + web service REST + Spring security. 1 – Création des objets métiers.
Voici le premier billet d’une liste dans laquelle j’expliquerai comment mettre en place un projet comprenant un Web Service REST (avec Restlet), protégé par Spring Security. Avec hibernate pour l’accès aux données et Spring pour l’injection de dépendances.
La base de données utilisée est MySQL. Maven est utilisé dans ce projet.
Dans ce premier billet, je vais décrire comment mettre en place le projet puis j’expliquerai la création des objets métiers.
Je vais faire la démonstration sous la forme d’un projet que je viens d’entamer : un projet de web service permettant à des utilisateurs de prendre des notes (sous forme de simple String, ou sous forme de fichier, par exemple un fichier de son). Il s’agit d’un projet qui n’est pas encore abouti, il y aura donc les bases, et notamment les bases d’un web service REST, mais pas un projet complètement fonctionnel.
Première chose à définir : le pom qui va permettre de gérer toutes les dépendances :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelversion>4.0.0</modelversion> <groupid>fr.mael</groupid> <artifactid>JFreeNote</artifactid> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>JFreeNote Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <restlet.version>1.1.10</restlet.version> <org.springframework.version>3.0.3.RELEASE</org.springframework.version> <spring-security.version>3.0.2.RELEASE</spring-security.version> </properties> <repositories> <repository> <id>maven-restlet</id> <name>Public online Restlet repository</name> <url>http://maven.restlet.org</url> </repository> </repositories> <dependencies> <dependency> <groupid>org.restlet</groupid> <artifactid>org.restlet</artifactid> <version>${restlet.version}</version> </dependency> <dependency> <groupid>org.restlet</groupid> <artifactid>org.restlet.ext.spring</artifactid> <version>${restlet.version}</version> </dependency> <dependency> <groupid>com.noelios.restlet</groupid> <artifactid>com.noelios.restlet.ext.servlet</artifactid> <version>${restlet.version}</version> </dependency> <dependency> <groupid>com.noelios.restlet</groupid> <artifactid>com.noelios.restlet.ext.spring</artifactid> <version>${restlet.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-core</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-expression</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-beans</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context-support</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-orm</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-web</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-core</artifactid> <version>${spring-security.version}</version> <scope>compile</scope> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-config</artifactid> <version>${spring-security.version}</version> <scope>compile</scope> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-taglibs</artifactid> <version>${spring-security.version}</version> <scope>compile</scope> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-web</artifactid> <version>${spring-security.version}</version> <scope>compile</scope> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-core</artifactid> <version>3.6.4.Final</version> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-commons-annotations</artifactid> <version>3.2.0.Final</version> </dependency> <dependency> <groupid>commons-logging</groupid> <artifactid>commons-logging</artifactid> <version>1.1</version> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.6.1</version> </dependency> <dependency> <groupid>commons-dbcp</groupid> <artifactid>commons-dbcp</artifactid> <version>1.2.2</version> </dependency> <dependency> <groupid>javassist</groupid> <artifactid>javassist</artifactid> <version>3.12.0.GA</version> </dependency> <dependency> <groupid>cglib</groupid> <artifactid>cglib</artifactid> <version>2.2.2</version> </dependency> <dependency> <groupid>com.thoughtworks.xstream</groupid> <artifactid>xstream</artifactid> <version>1.3.1</version> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version>5.1.16</version> </dependency> </dependencies> </project>
Je ne détaille pas toutes les dépendances. Il y a en gros les dépendances pour Spring, Spring security, Restlet, Hibernate, Mysql. (il est possible qu’il y ait des dépendances inutiles, étant donné que j’ai copié les différentes partie du pom depuis un autre projet).
Deuxième chose à définir : les objets qui seront manipulés.
En premier, l’interface commune à ces objets :
public interface IOM extends Serializable { }
On définit ensuite une première « superclasse », qui englobera tous les objets manipulés
@MappedSuperclass public class OMBase implements IOM { private Integer identifier; @Id @GeneratedValue @Column(name = "identifier", unique = true, nullable = false) public Integer getIdentifier() { return identifier; } public void setIdentifier(Integer identifier) { this.identifier = identifier; } }
On voit que des annotations sont présentes. Il s’agit d’annotations liées à la persistance de données (hibernate). L’annotation @MappedSuperclass permet d’indiquer que les classes qui hériteront de cette superclasse hériteront également de son mapping. Les autres annotations indiquent la relation pour l’identifiant.
On définit ensuite une deuxième superclasse, qui permettra d’englober les objets qui sont liés à un utilisateur :
@MappedSuperclass public class UserOMBase extends OMBase { private User user; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_user", nullable = false) public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
Comme on peut le voir, cette superclasse hérite de la précédente, elle hérite donc de son mapping (identifiant), et elle ajoute une relation vers un utilisateur.
On va maintenant définir les classes des objets métiers, qui hériteront des différentes classes décrites ci-dessus.
En premier lieu, l’utilisateur :
@Entity @Table(name = "user") public class User extends OMBase implements UserDetails { /** * The login of the user, used for authentication */ private String login; /** * The password of the user, used for authentication */ private String password; /** * The mail address of the user */ private String mailAddress; /** * The creation date of the user */ private Date creationDate; @Column(name = "login") public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } @Column(name = "password") public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Column(name = "mail_address") public String getMailAddress() { return mailAddress; } public void setMailAddress(String mailAddress) { this.mailAddress = mailAddress; } @Column(name = "creation_date") public Date getCreationDate() { return creationDate; } public void setCreationDate(Date creationDate) { this.creationDate = creationDate; } @Override @Transient public Collection<GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return null; } @Transient @Override public String getUsername() { // TODO Auto-generated method stub return login; } @Transient @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } @Transient @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } @Transient @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } @Transient @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } }
Cette classe hérite de OMBase (et pas de UserOMBase, puisqu’un utilisateur ne possède pas d’utilisateur). Elle implémente UserDetails. Ceci est obligatoire pour que Spring Security reconnaisse notre User comme un « principal ». On voit que le fait d’implémenter cette interface ajoute un certain nombre de méthodes, principalement des getters, auxquels on ajoute l’annotation @Transient, car ils ne sont pas mappés en base (pas dans mon projet, en tout cas). Les autres getters sont mappés grâce à l’annotation @Column. A noter que si le nom de la propriété est le même que le nom du champ en base, il n’est pas nécessaire d’ajouter l’annotation (on peut le faire quand même, par soucis de clarté).
On définit ensuite une deuxième classe : la classe Note
@Entity @Table(name = "note") public class Note extends UserOMBase { /** * content of the note */ private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
La classe est très simple. Pour illustrer ce que je disais précédemment, je n’ai pas mis d’annotation sur le getter de la propriété « content ». Elle sera tout de même reconnue, parce qu’elle a le même nom en base. Comme on peut le voir, cette fois, la classe hérite de UserOMBase, ce qui veut dire qu’une note est reliée à un utilisateur.
Puis on définit une troisième classe : File, qui représentera les fichiers de l’utilisateur.
@Entity @Table(name = "file") public class File extends UserOMBase { /** * File type of the file */ private FileType fileType; /** * content of the file */ private byte[] content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_file_type") public FileType getFileType() { return fileType; } public void setFileType(FileType fileType) { this.fileType = fileType; } @Column(name = "content") public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } }
Là encore, la classe est très simple. Et là encore, elle hérite de UserOMBase.
Et enfin, dernière classe que l’on définit, le classe FileType :
@Entity @Table(name = "file_type") public class FileType extends OMBase { /** * Label of the type */ private String label; public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } }
Voilà, c’est fait pour la création des objets métiers, et c’est donc fini pour ce billet.
La suite dans un prochain épisode.