Commit e4068799 authored by DavidSholito's avatar DavidSholito

Proyecto base

Proyecto base dado por el profesor
parents
# General
/bak
# Eclipse
.project
.classpath
.settings
WebContent
# Maven
/bin
/target
/assembly
# Testing
/servers
C:\\nppdf32Log\\debuglog.txt
# Angular
src/main/angular
This diff is collapsed.
DAAExample
==========
Aplicación y arquitectura de ejemplo para la asignatura Desarrollo Ágil de
Aplicaciones del Grado en Ingeniería Informática de la Escuela Superior de
Ingeniería Informática de la Universidad de Vigo.
## Dependencias
Este proyecto está diseñado para ser desarrollado en un entorno con:
* Maven 3
* Java 8
* MySQL 5.7.6+ o 8+
Además, se recomienda emplear la última versión de Eclipse IDE for Enterprise
Java Developers.
## Ejecución con Maven
La configuración de Maven ha sido preparada para permitir varios tipos de
ejecución.
### Ejecución de la aplicación con Tomcat y MySQL
El proyecto está configurado para poder ejecutar la aplicación sin tener que
realizar ninguna configuración adicional salvo tener disponible un servidor
MySQL en local.
Los ficheros del proyecto `db/mysql.sql` y 'db/mysql-with-inserts.sql' contienen
todas las consultas necesarias para crear la base de datos y el usuario
requeridos, con o sin datos de ejemplo, respectivamente. Por lo tanto, podemos
configurar inicialmente la base de datos con cualquiera de los siguientes
comandos (desde la raíz el proyecto):
* Sin datos: `mysql -u root -p < db/mysql.sql`
* Con datos: `mysql -u root -p < db/mysql-with-inserts.sql`
Una vez configurada la base de datos podemos lanzar la ejecución con el comando:
`mvn -Prun -DskipTests=true package cargo:run`
La aplicación se servirá en la URL local: http://localhost:9080/DAAExample
Para detener la ejecución podemos utilizar `Ctrl+C`.
### Ejecución de la aplicación con Tomcat y MySQL con redespliegue automático
Durante el desarrollo es interesante que la apliación se redespliegue de forma
automática cada vez que se hace un cambio. Para ello podemos utilizar el
siguiente comand:
`mvn -Prun -DskipTests=true package cargo:start fizzed-watcher:run`
La aplicación se servirá en la URL local: http://localhost:9080/DAAExample
Para detener la ejecución podemos utilizar `Ctrl+C`.
### Construcción con tests de unidad e integración
En esta construcción se ejecutarán todos los tests relacionados con el backend:
* **Unidad**: se utilizan para testear las entidades y las capas DAO y REST de
forma aislada.
* **Integración**: se utilizan para testear las capas REST y DAO de forma
integrada. Para este tipo de pruebas se utiliza una base de datos HSQL en
memoria.
El comando para lanzar esta construcción es:
`mvn install`
DROP DATABASE IF EXISTS `daaexample`;
CREATE DATABASE `daaexample`;
CREATE TABLE `daaexample`.`people` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`surname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL,
`password` varchar(64) NOT NULL,
`role` varchar(10) NOT NULL,
PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE USER IF NOT EXISTS 'daa'@'localhost' IDENTIFIED WITH mysql_native_password BY 'daa';
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost';
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Antón','Pérez');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Manuel','Martínez');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Laura','Reboredo');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Perico','Palotes');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Ana','María');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'María','Nuevo');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Alba','Fernández');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Asunción','Jiménez');
-- The password for each user is its login suffixed with "pass". For example, user "admin" has the password "adminpass".
INSERT INTO `daaexample`.`users` (`login`,`password`,`role`)
VALUES ('admin', '713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca','ADMIN');
INSERT INTO `daaexample`.`users` (`login`,`password`,`role`)
VALUES ('normal', '7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83','USER');
DROP DATABASE IF EXISTS `daaexample`;
CREATE DATABASE `daaexample`;
CREATE TABLE `daaexample`.`people` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`surname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL,
`password` varchar(64) NOT NULL,
`role` varchar(10) NOT NULL,
PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE USER IF NOT EXISTS 'daa'@'localhost' IDENTIFIED WITH mysql_native_password BY 'daa';
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost';
This diff is collapsed.
package es.uvigo.esei.daa;
import static java.util.stream.Collectors.toSet;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import es.uvigo.esei.daa.rest.PeopleResource;
import es.uvigo.esei.daa.rest.UsersResource;
/**
* Configuration of the REST application. This class includes the resources and
* configuration parameter used in the REST API of the application.
*
* @author Miguel Reboiro Jato
*
*/
@ApplicationPath("/rest/*")
public class DAAExampleApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return Stream.of(
PeopleResource.class,
UsersResource.class
).collect(toSet());
}
@Override
public Map<String, Object> getProperties() {
// Activates JSON automatic conversion in JAX-RS
return Collections.singletonMap(
"com.sun.jersey.api.json.POJOMappingFeature", true
);
}
}
package es.uvigo.esei.daa.dao;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
/**
* Simple base class for DAO (Data Access Object) classes. This super-class is
* responsible for providing a {@link java.sql.Connection} to its sub-classes.
*
* @author Miguel Reboiro Jato
*
*/
public abstract class DAO {
private final static Logger LOG = Logger.getLogger(DAO.class.getName());
private final static String JNDI_NAME = "java:/comp/env/jdbc/daaexample";
private DataSource dataSource;
/**
* Constructs a new instance of {@link DAO}.
*/
public DAO() {
try {
this.dataSource = (DataSource) new InitialContext().lookup(JNDI_NAME);
} catch (NamingException e) {
LOG.log(Level.SEVERE, "Error initializing DAO", e);
throw new RuntimeException(e);
}
}
/**
* Returns an open {@link java.sql.Connection}.
*
* @return an open {@link java.sql.Connection}.
* @throws SQLException if an error happens while establishing the
* connection with the database.
*/
protected Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
}
package es.uvigo.esei.daa.dao;
/**
* A general exception class for the DAO layer.
*
* @author Miguel Reboiro Jato
*/
public class DAOException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Constructs a new instance of {@link DAOException} with {@code null} as
* its detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
*/
public DAOException() {
}
/**
* Constructs a new instance of {@link DAOException} with the specified
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for later
* retrieval by the {@link #getMessage()} method.
*/
public DAOException(String message) {
super(message);
}
/**
* Constructs a new instance of {@link DAOException} with the specified
* cause and a detail message of
* {@code (cause==null ? null : cause.toString())} (which typically contains
* the class and detail message of {@code cause}). This constructor is
* useful for exceptions that are little more than wrappers for other
* throwables (for example, {@link java.security.PrivilegedActionException}).
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is permitted, and
* indicates that the cause is nonexistent or unknown.)
*/
public DAOException(Throwable cause) {
super(cause);
}
/**
* Constructs a new instance of {@link DAOException} with the specified
* detail message and cause.
*
* <p>Note that the detail message associated with {@code cause} is
* <i>not</i> automatically incorporated in this exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). A {@code null} value is permitted, and
* indicates that the cause is nonexistent or unknown.
*/
public DAOException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new instance of {@link DAOException} with the specified
* detail message, cause, suppression enabled or disabled, and writable
* stack trace enabled or disabled.
*
* @param message the detail message.
* @param cause the cause. A {@code null} value is permitted, and indicates
* that the cause is nonexistent or unknown.
* @param enableSuppression whether or not suppression is enabled or
* disabled.
* @param writableStackTrace whether or not the stack trace should be
* writable.
*/
public DAOException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package es.uvigo.esei.daa.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import es.uvigo.esei.daa.entities.Person;
/**
* DAO class for the {@link Person} entities.
*
* @author Miguel Reboiro Jato
*
*/
public class PeopleDAO extends DAO {
private final static Logger LOG = Logger.getLogger(PeopleDAO.class.getName());
/**
* Returns a person stored persisted in the system.
*
* @param id identifier of the person.
* @return a person with the provided identifier.
* @throws DAOException if an error happens while retrieving the person.
* @throws IllegalArgumentException if the provided id does not corresponds
* with any persisted person.
*/
public Person get(int id)
throws DAOException, IllegalArgumentException {
try (final Connection conn = this.getConnection()) {
final String query = "SELECT * FROM people WHERE id=?";
try (final PreparedStatement statement = conn.prepareStatement(query)) {
statement.setInt(1, id);
try (final ResultSet result = statement.executeQuery()) {
if (result.next()) {
return rowToEntity(result);
} else {
throw new IllegalArgumentException("Invalid id");
}
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error getting a person", e);
throw new DAOException(e);
}
}
/**
* Returns a list with all the people persisted in the system.
*
* @return a list with all the people persisted in the system.
* @throws DAOException if an error happens while retrieving the people.
*/
public List<Person> list() throws DAOException {
try (final Connection conn = this.getConnection()) {
final String query = "SELECT * FROM people";
try (final PreparedStatement statement = conn.prepareStatement(query)) {
try (final ResultSet result = statement.executeQuery()) {
final List<Person> people = new LinkedList<>();
while (result.next()) {
people.add(rowToEntity(result));
}
return people;
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error listing people", e);
throw new DAOException(e);
}
}
/**
* Persists a new person in the system. An identifier will be assigned
* automatically to the new person.
*
* @param name name of the new person. Can't be {@code null}.
* @param surname surname of the new person. Can't be {@code null}.
* @return a {@link Person} entity representing the persisted person.
* @throws DAOException if an error happens while persisting the new person.
* @throws IllegalArgumentException if the name or surname are {@code null}.
*/
public Person add(String name, String surname)
throws DAOException, IllegalArgumentException {
if (name == null || surname == null) {
throw new IllegalArgumentException("name and surname can't be null");
}
try (Connection conn = this.getConnection()) {
final String query = "INSERT INTO people VALUES(null, ?, ?)";
try (PreparedStatement statement = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
statement.setString(1, name);
statement.setString(2, surname);
if (statement.executeUpdate() == 1) {
try (ResultSet resultKeys = statement.getGeneratedKeys()) {
if (resultKeys.next()) {
return new Person(resultKeys.getInt(1), name, surname);
} else {
LOG.log(Level.SEVERE, "Error retrieving inserted id");
throw new SQLException("Error retrieving inserted id");
}
}
} else {
LOG.log(Level.SEVERE, "Error inserting value");
throw new SQLException("Error inserting value");
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error adding a person", e);
throw new DAOException(e);
}
}
/**
* Modifies a person previously persisted in the system. The person will be
* retrieved by the provided id and its current name and surname will be
* replaced with the provided.
*
* @param person a {@link Person} entity with the new data.
* @throws DAOException if an error happens while modifying the new person.
* @throws IllegalArgumentException if the person is {@code null}.
*/
public void modify(Person person)
throws DAOException, IllegalArgumentException {
if (person == null) {
throw new IllegalArgumentException("person can't be null");
}
try (Connection conn = this.getConnection()) {
final String query = "UPDATE people SET name=?, surname=? WHERE id=?";
try (PreparedStatement statement = conn.prepareStatement(query)) {
statement.setString(1, person.getName());
statement.setString(2, person.getSurname());
statement.setInt(3, person.getId());
if (statement.executeUpdate() != 1) {
throw new IllegalArgumentException("name and surname can't be null");
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error modifying a person", e);
throw new DAOException();
}
}
/**
* Removes a persisted person from the system.
*
* @param id identifier of the person to be deleted.
* @throws DAOException if an error happens while deleting the person.
* @throws IllegalArgumentException if the provided id does not corresponds
* with any persisted person.
*/
public void delete(int id)
throws DAOException, IllegalArgumentException {
try (final Connection conn = this.getConnection()) {
final String query = "DELETE FROM people WHERE id=?";
try (final PreparedStatement statement = conn.prepareStatement(query)) {
statement.setInt(1, id);
if (statement.executeUpdate() != 1) {
throw new IllegalArgumentException("Invalid id");
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error deleting a person", e);
throw new DAOException(e);
}
}
private Person rowToEntity(ResultSet row) throws SQLException {
return new Person(
row.getInt("id"),
row.getString("name"),
row.getString("surname")
);
}
}
package es.uvigo.esei.daa.dao;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import es.uvigo.esei.daa.entities.User;
/**
* DAO class for managing the users of the system.
*
* @author Miguel Reboiro Jato
*/
public class UsersDAO extends DAO {
private final static Logger LOG = Logger.getLogger(UsersDAO.class.getName());
/**
* Returns a user stored persisted in the system.
*
* @param login the login of the user to be retrieved.
* @return a user with the provided login.
* @throws DAOException if an error happens while retrieving the user.
* @throws IllegalArgumentException if the provided login does not
* corresponds with any persisted user.
*/
public User get(String login) throws DAOException {
try (final Connection conn = this.getConnection()) {
final String query = "SELECT * FROM users WHERE login=?";
try (final PreparedStatement statement = conn.prepareStatement(query)) {
statement.setString(1, login);
try (final ResultSet result = statement.executeQuery()) {
if (result.next()) {
return rowToEntity(result);
} else {
throw new IllegalArgumentException("Invalid id");
}
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error checking login", e);
throw new DAOException(e);
}
}
/**
* Checks if the provided credentials (login and password) correspond with a
* valid user registered in the system.
*
* <p>The password is stored in the system "salted" and encoded with the
* SHA-256 algorithm.</p>
*
* @param login the login of the user.
* @param password the password of the user.
* @return {@code true} if the credentials are valid. {@code false}
* otherwise.
* @throws DAOException if an error happens while checking the credentials.
*/
public boolean checkLogin(String login, String password) throws DAOException {
try {
final User user = this.get(login);
final String dbPassword = user.getPassword();
final String shaPassword = encodeSha256(password);
return shaPassword.equals(dbPassword);
} catch (IllegalArgumentException iae) {
return false;
}
}
private final static String encodeSha256(String text) {
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] digested = digest.digest(text.getBytes());
return hexToString(digested);
} catch (NoSuchAlgorithmException e) {
LOG.log(Level.SEVERE, "SHA-256 not supported", e);
throw new RuntimeException(e);
}
}
private final static String hexToString(byte[] hex) {
final StringBuilder sb = new StringBuilder();
for (byte b : hex) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
private User rowToEntity(ResultSet result) throws SQLException {
return new User(
result.getString("login"),
result.getString("password"),
result.getString("role")
);
}
}
package es.uvigo.esei.daa.entities;
import static java.util.Objects.requireNonNull;
/**
* An entity that represents a person.
*
* @author Miguel Reboiro Jato
*/
public class Person {
private int id;
private String name;
private String surname;
// Constructor needed for the JSON conversion
Person() {}
/**
* Constructs a new instance of {@link Person}.
*
* @param id identifier of the person.
* @param name name of the person.
* @param surname surname of the person.
*/
public Person(int id, String name, String surname) {
this.id = id;
this.setName(name);
this.setSurname(surname);
}
/**
* Returns the identifier of the person.
*
* @return the identifier of the person.
*/
public int getId() {
return id;
}
/**
* Returns the name of the person.
*
* @return the name of the person.
*/
public String getName() {
return name;
}
/**
* Set the name of this person.
*
* @param name the new name of the person.
* @throws NullPointerException if the {@code name} is {@code null}.
*/
public void setName(String name) {
this.name = requireNonNull(name, "Name can't be null");
}
/**
* Returns the surname of the person.
*
* @return the surname of the person.
*/
public String getSurname() {
return surname;
}
/**
* Set the surname of this person.
*
* @param surname the new surname of the person.
* @throws NullPointerException if the {@code surname} is {@code null}.
*/
public void setSurname(String surname) {
this.surname = requireNonNull(surname, "Surname can't be null");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Person))
return false;
Person other = (Person) obj;
if (id != other.id)
return false;
return true;
}
}
package es.uvigo.esei.daa.entities;
import static java.util.Objects.requireNonNull;
/**
* An entity that represents a user.
*
* @author Miguel Reboiro Jato
*/
public class User {
private String login;
private String password;
private String role;
// Constructor needed for the JSON conversion
User() {}
/**
* Constructs a new instance of {@link User}.
*
* @param login login that identifies the user in the system.
* @param password password of the user encoded using SHA-256 and with the
* "salt" prefix added.
*/
public User(String login, String password, String role) {
this.setLogin(login);
this.setPassword(password);
this.setRole(role);
}
/**
* Returns the login of the user.
*
* @return the login of the user.
*/
public String getLogin() {
return login;
}
/**
* Sets the login of the user.
*
* @param login the login that identifies the user in the system.
*/
public void setLogin(String login) {
this.login = requireNonNull(login, "Login can't be null");
}
/**
* Returns the password of the user.
*
* @return the password of the user.
*/
public String getPassword() {
return password;
}
/**
* Sets the users password.
* @param password the password of the user encoded using SHA-256 and with
* the "salt" prefix added.
*/
public void setPassword(String password) {
requireNonNull(password, "Password can't be null");
if (!password.matches("[a-zA-Z0-9]{64}"))
throw new IllegalArgumentException("Password must be a valid SHA-256");
this.password = password;
}
/**
* Returns the role of the user.
*
* @return the role of the user.
*/
public String getRole() {
return role;
}
/**
* Sets the role of the user.
*
* @param role the role of the user
*/
public void setRole(String role) {
this.role = requireNonNull(role, "Role can't be null");
}
}
package es.uvigo.esei.daa.rest;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.PeopleDAO;
import es.uvigo.esei.daa.entities.Person;
/**
* REST resource for managing people.
*
* @author Miguel Reboiro Jato.
*/
@Path("/people")
@Produces(MediaType.APPLICATION_JSON)
public class PeopleResource {
private final static Logger LOG = Logger.getLogger(PeopleResource.class.getName());
private final PeopleDAO dao;
/**
* Constructs a new instance of {@link PeopleResource}.
*/
public PeopleResource() {
this(new PeopleDAO());
}
// Needed for testing purposes
PeopleResource(PeopleDAO dao) {
this.dao = dao;
}
/**
* Returns a person with the provided identifier.
*
* @param id the identifier of the person to retrieve.
* @return a 200 OK response with a person that has the provided identifier.
* If the identifier does not corresponds with any user, a 400 Bad Request
* response with an error message will be returned. If an error happens
* while retrieving the list, a 500 Internal Server Error response with an
* error message will be returned.
*/
@GET
@Path("/{id}")
public Response get(
@PathParam("id") int id
) {
try {
final Person person = this.dao.get(id);
return Response.ok(person).build();
} catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in get method", iae);
return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage())
.build();
} catch (DAOException e) {
LOG.log(Level.SEVERE, "Error getting a person", e);
return Response.serverError()
.entity(e.getMessage())
.build();
}
}
/**
* Returns the complete list of people stored in the system.
*
* @return a 200 OK response with the complete list of people stored in the
* system. If an error happens while retrieving the list, a 500 Internal
* Server Error response with an error message will be returned.
*/
@GET
public Response list() {
try {
return Response.ok(this.dao.list()).build();
} catch (DAOException e) {
LOG.log(Level.SEVERE, "Error listing people", e);
return Response.serverError().entity(e.getMessage()).build();
}
}
/**
* Creates a new person in the system.
*
* @param name the name of the new person.
* @param surname the surname of the new person.
* @return a 200 OK response with a person that has been created. If the
* name or the surname are not provided, a 400 Bad Request response with an
* error message will be returned. If an error happens while retrieving the
* list, a 500 Internal Server Error response with an error message will be
* returned.
*/
@POST
public Response add(
@FormParam("name") String name,
@FormParam("surname") String surname
) {
try {
final Person newPerson = this.dao.add(name, surname);
return Response.ok(newPerson).build();
} catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in add method", iae);
return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage())
.build();
} catch (DAOException e) {
LOG.log(Level.SEVERE, "Error adding a person", e);
return Response.serverError()
.entity(e.getMessage())
.build();
}
}
/**
* Modifies the data of a person.
*
* @param id identifier of the person to modify.
* @param name the new name of the person.
* @param surname the new surname of the person.
* @return a 200 OK response with a person that has been modified. If the
* identifier does not corresponds with any user or the name or surname are
* not provided, a 400 Bad Request response with an error message will be
* returned. If an error happens while retrieving the list, a 500 Internal
* Server Error response with an error message will be returned.
*/
@PUT
@Path("/{id}")
public Response modify(
@PathParam("id") int id,
@FormParam("name") String name,
@FormParam("surname") String surname
) {
try {
final Person modifiedPerson = new Person(id, name, surname);
this.dao.modify(modifiedPerson);
return Response.ok(modifiedPerson).build();
} catch (NullPointerException npe) {
final String message = String.format("Invalid data for person (name: %s, surname: %s)", name, surname);
LOG.log(Level.FINE, message);
return Response.status(Response.Status.BAD_REQUEST)
.entity(message)
.build();
} catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in modify method", iae);
return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage())
.build();
} catch (DAOException e) {
LOG.log(Level.SEVERE, "Error modifying a person", e);
return Response.serverError()
.entity(e.getMessage())
.build();
}
}
/**
* Deletes a person from the system.
*
* @param id the identifier of the person to be deleted.
* @return a 200 OK response with the identifier of the person that has
* been deleted. If the identifier does not corresponds with any user, a 400
* Bad Request response with an error message will be returned. If an error
* happens while retrieving the list, a 500 Internal Server Error response
* with an error message will be returned.
*/
@DELETE
@Path("/{id}")
public Response delete(
@PathParam("id") int id
) {
try {
this.dao.delete(id);
return Response.ok(id).build();
} catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in delete method", iae);
return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage())
.build();
} catch (DAOException e) {
LOG.log(Level.SEVERE, "Error deleting a person", e);
return Response.serverError()
.entity(e.getMessage())
.build();
}
}
}
package es.uvigo.esei.daa.rest;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.UsersDAO;
/**
* REST resource for managing users.
*
* @author Miguel Reboiro Jato.
*/
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
public class UsersResource {
private final static Logger LOG = Logger.getLogger(UsersResource.class.getName());
private final UsersDAO dao;
private @Context SecurityContext security;
/**
* Constructs a new instance of {@link UsersResource}.
*/
public UsersResource() {
this(new UsersDAO());
}
// Needed for testing purposes
UsersResource(UsersDAO dao) {
this(dao, null);
}
// Needed for testing purposes
UsersResource(UsersDAO dao, SecurityContext security) {
this.dao = dao;
this.security = security;
}
/**
* Returns a user with the provided login.
*
* @param login the identifier of the user to retrieve.
* @return a 200 OK response with an user that has the provided login.
* If the request is done without providing the login credentials or using
* invalid credentials a 401 Unauthorized response will be returned. If the
* credentials are provided and a regular user (i.e. non admin user) tries
* to access the data of other user, a 403 Forbidden response will be
* returned. If the credentials are OK, but the login does not corresponds
* with any user, a 400 Bad Request response with an error message will be
* returned. If an error happens while retrieving the list, a 500 Internal
* Server Error response with an error message will be returned.
*/
@GET
@Path("/{login}")
public Response get(
@PathParam("login") String login
) {
final String loggedUser = getLogin();
// Each user can only access his or her own data. Only the admin user
// can access the data of any user.
if (loggedUser.equals(login) || this.isAdmin()) {
try {
return Response.ok(dao.get(login)).build();
} catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid user login in get method", iae);
return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage())
.build();
} catch (DAOException e) {
LOG.log(Level.SEVERE, "Error getting an user", e);
return Response.serverError()
.entity(e.getMessage())
.build();
}
} else {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
private String getLogin() {
return this.security.getUserPrincipal().getName();
}
private boolean isAdmin() {
return this.security.isUserInRole("ADMIN");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>DAAExample</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Authorization</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET, POST, DELETE, PUT</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/rest/*</url-pattern>
</filter-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/rest/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
<role-name>USER</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/rest/people/*</url-pattern>
<http-method>GET</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<!-- Security roles referenced by this web application -->
<security-role>
<role-name>ADMIN</role-name>
</security-role>
<security-role>
<role-name>USER</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DAAExample</realm-name>
</login-config>
</web-app>
\ No newline at end of file
html, body {
height: 100%;
}
body {
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
}
#form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
#form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
#form-signin .form-control:focus {
z-index: 2;
}
#login {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
#password {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>DAA Example - Login</title>
<link rel="stylesheet" href="css/login.css">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body class="text-center">
<form id="form-singin">
<h1 class="h1 mb-3 font-weight-normal">DAA Example</h1>
<label for="login" class="sr-only">Usuario</label> <input id="login"
name="login" type="text" class="form-control" placeholder="Usuario"
required autofocus /> <label for="password" class="sr-only">Contraseña</label>
<input id="password" name="password" type="password"
class="form-control" placeholder="Contraseña" required />
<button type="submit" class="btn btn-lg btn-primary btn-block mt-3">Entrar</button>
</form>
<script type="text/javascript"
src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
<script type="text/javascript" src="js/login.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#form-singin').submit(function(event) {
event.preventDefault();
var login = $('#login').val();
var password = $('#password').val();
doLogin(login, password);
});
});
</script>
</body>
</html>
\ No newline at end of file
var PeopleDAO = (function() {
var resourcePath = "rest/people/";
var requestByAjax = function(data, done, fail, always) {
done = typeof done !== 'undefined' ? done : function() {};
fail = typeof fail !== 'undefined' ? fail : function() {};
always = typeof always !== 'undefined' ? always : function() {};
let authToken = localStorage.getItem('authorization-token');
if (authToken !== null) {
data.beforeSend = function(xhr) {
xhr.setRequestHeader('Authorization', 'Basic ' + authToken);
};
}
$.ajax(data).done(done).fail(fail).always(always);
};
function PeopleDAO() {
this.listPeople = function(done, fail, always) {
requestByAjax({
url : resourcePath,
type : 'GET'
}, done, fail, always);
};
this.addPerson = function(person, done, fail, always) {
requestByAjax({
url : resourcePath,
type : 'POST',
data : person
}, done, fail, always);
};
this.modifyPerson = function(person, done, fail, always) {
requestByAjax({
url : resourcePath + person.id,
type : 'PUT',
data : person
}, done, fail, always);
};
this.deletePerson = function(id, done, fail, always) {
requestByAjax({
url : resourcePath + id,
type : 'DELETE',
}, done, fail, always);
};
}
return PeopleDAO;
})();
\ No newline at end of file
function doLogin(login, password) {
$.ajax({
url: 'rest/users/' + login,
type: 'GET',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(login + ":" + password));
}
})
.done(function() {
localStorage.setItem('authorization-token', btoa(login + ":" + password));
window.location = 'main.html';
})
.fail(function() {
alert('Invalid login and/or password.');
});
}
function doLogout() {
localStorage.removeItem('authorization-token');
window.location = 'index.html';
}
\ No newline at end of file
var PeopleView = (function() {
var dao;
// Referencia a this que permite acceder a las funciones públicas desde las funciones de jQuery.
var self;
var formId = 'people-form';
var listId = 'people-list';
var formQuery = '#' + formId;
var listQuery = '#' + listId;
function PeopleView(peopleDao, formContainerId, listContainerId) {
dao = peopleDao;
self = this;
insertPeopleForm($('#' + formContainerId));
insertPeopleList($('#' + listContainerId));
this.init = function() {
dao.listPeople(function(people) {
$.each(people, function(key, person) {
appendToTable(person);
});
},
function() {
alert('No has sido posible acceder al listado de personas.');
});
// La acción por defecto de enviar formulario (submit) se sobreescribe
// para que el envío sea a través de AJAX
$(formQuery).submit(function(event) {
var person = self.getPersonInForm();
if (self.isEditing()) {
dao.modifyPerson(person,
function(person) {
$('#person-' + person.id + ' td.name').text(person.name);
$('#person-' + person.id + ' td.surname').text(person.surname);
self.resetForm();
},
showErrorMessage,
self.enableForm
);
} else {
dao.addPerson(person,
function(person) {
appendToTable(person);
self.resetForm();
},
showErrorMessage,
self.enableForm
);
}
return false;
});
$('#btnClear').click(this.resetForm);
};
this.getPersonInForm = function() {
var form = $(formQuery);
return {
'id': form.find('input[name="id"]').val(),
'name': form.find('input[name="name"]').val(),
'surname': form.find('input[name="surname"]').val()
};
};
this.getPersonInRow = function(id) {
var row = $('#person-' + id);
if (row !== undefined) {
return {
'id': id,
'name': row.find('td.name').text(),
'surname': row.find('td.surname').text()
};
} else {
return undefined;
}
};
this.editPerson = function(id) {
var row = $('#person-' + id);
if (row !== undefined) {
var form = $(formQuery);
form.find('input[name="id"]').val(id);
form.find('input[name="name"]').val(row.find('td.name').text());
form.find('input[name="surname"]').val(row.find('td.surname').text());
$('input#btnSubmit').val('Modificar');
}
};
this.deletePerson = function(id) {
if (confirm('Está a punto de eliminar a una persona. ¿Está seguro de que desea continuar?')) {
dao.deletePerson(id,
function() {
$('tr#person-' + id).remove();
},
showErrorMessage
);
}
};
this.isEditing = function() {
return $(formQuery + ' input[name="id"]').val() != "";
};
this.disableForm = function() {
$(formQuery + ' input').prop('disabled', true);
};
this.enableForm = function() {
$(formQuery + ' input').prop('disabled', false);
};
this.resetForm = function() {
$(formQuery)[0].reset();
$(formQuery + ' input[name="id"]').val('');
$('#btnSubmit').val('Crear');
};
};
var insertPeopleList = function(parent) {
parent.append(
'<table id="' + listId + '" class="table">\
<thead>\
<tr class="row">\
<th class="col-sm-4">Nombre</th>\
<th class="col-sm-5">Apellido</th>\
<th class="col-sm-3">&nbsp;</th>\
</tr>\
</thead>\
<tbody>\
</tbody>\
</table>'
);
};
var insertPeopleForm = function(parent) {
parent.append(
'<form id="' + formId + '" class="mb-5 mb-10">\
<input name="id" type="hidden" value=""/>\
<div class="row">\
<div class="col-sm-4">\
<input name="name" type="text" value="" placeholder="Nombre" class="form-control" required/>\
</div>\
<div class="col-sm-5">\
<input name="surname" type="text" value="" placeholder="Apellido" class="form-control" required/>\
</div>\
<div class="col-sm-3">\
<input id="btnSubmit" type="submit" value="Crear" class="btn btn-primary" />\
<input id="btnClear" type="reset" value="Limpiar" class="btn" />\
</div>\
</div>\
</form>'
);
};
var createPersonRow = function(person) {
return '<tr id="person-'+ person.id +'" class="row">\
<td class="name col-sm-4">' + person.name + '</td>\
<td class="surname col-sm-5">' + person.surname + '</td>\
<td class="col-sm-3">\
<a class="edit btn btn-primary" href="#">Editar</a>\
<a class="delete btn btn-warning" href="#">Eliminar</a>\
</td>\
</tr>';
};
var showErrorMessage = function(jqxhr, textStatus, error) {
alert(textStatus + ": " + error);
};
var addRowListeners = function(person) {
$('#person-' + person.id + ' a.edit').click(function() {
self.editPerson(person.id);
});
$('#person-' + person.id + ' a.delete').click(function() {
self.deletePerson(person.id);
});
};
var appendToTable = function(person) {
$(listQuery + ' > tbody:last')
.append(createPersonRow(person));
addRowListeners(person);
};
return PeopleView;
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DAA Example</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
<header>
<div class="navbar navbar-dark bg-dark box-shadow">
<div class="container d-flex justify-content-between">
<a href="#" class="navbar-brand d-flex align-items-center">
<strong>DAA Example</strong>
</a>
<button id="logout" class="btn btn-dark">Cerrar sesión</button>
</div>
</div>
</header>
<div class="container">
<div id="people-container">
<h1 class="display-5 mt-3 mb-3">Personas</h1>
</div>
</div>
<script type="text/javascript"
src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
<script type="text/javascript" src="js/dao/people.js"></script>
<script type="text/javascript" src="js/view/people.js"></script>
<script type="text/javascript" src="js/login.js"></script>
<script type="text/javascript">
$(document).ready(
function() {
$('#logout').click(function(event) {
event.preventDefault();
doLogout();
});
var view = new PeopleView(new PeopleDAO(),
'people-container', 'people-container'
);
view.init();
});
</script>
</body>
</html>
\ No newline at end of file
package es.uvigo.esei.daa;
import static java.util.Collections.unmodifiableSet;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import es.uvigo.esei.daa.filters.AuthorizationFilter;
@ApplicationPath("/rest/*")
public class DAAExampleTestApplication extends DAAExampleApplication {
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>(super.getClasses());
classes.add(AuthorizationFilter.class);
return unmodifiableSet(classes);
}
}
package es.uvigo.esei.daa.dataset;
import static java.util.Arrays.binarySearch;
import static java.util.Arrays.stream;
import java.util.Arrays;
import java.util.function.Predicate;
import es.uvigo.esei.daa.entities.Person;
public final class PeopleDataset {
private PeopleDataset() {}
public static Person[] people() {
return new Person[] {
new Person(1, "Antón", "Álvarez"),
new Person(2, "Ana", "Amargo"),
new Person(3, "Manuel", "Martínez"),
new Person(4, "María", "Márquez"),
new Person(5, "Lorenzo", "López"),
new Person(6, "Laura", "Laredo"),
new Person(7, "Perico", "Palotes"),
new Person(8, "Patricia", "Pérez"),
new Person(9, "Julia", "Justa"),
new Person(10, "Juan", "Jiménez")
};
}
public static Person[] peopleWithout(int ... ids) {
Arrays.sort(ids);
final Predicate<Person> hasValidId = person ->
binarySearch(ids, person.getId()) < 0;
return stream(people())
.filter(hasValidId)
.toArray(Person[]::new);
}
public static Person person(int id) {
return stream(people())
.filter(person -> person.getId() == id)
.findAny()
.orElseThrow(IllegalArgumentException::new);
}
public static int existentId() {
return 5;
}
public static int nonExistentId() {
return 1234;
}
public static Person existentPerson() {
return person(existentId());
}
public static Person nonExistentPerson() {
return new Person(nonExistentId(), "Jane", "Smith");
}
public static String newName() {
return "John";
}
public static String newSurname() {
return "Doe";
}
public static Person newPerson() {
return new Person(people().length + 1, newName(), newSurname());
}
}
package es.uvigo.esei.daa.dataset;
import java.util.Arrays;
import java.util.Base64;
import es.uvigo.esei.daa.entities.User;
public final class UsersDataset {
private UsersDataset() {}
public static User[] users() {
return new User[] {
new User(adminLogin(), "713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca", "ADMIN"),
new User(normalLogin(), "7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83", "USER")
};
}
public static User user(String login) {
return Arrays.stream(users())
.filter(user -> user.getLogin().equals(login))
.findAny()
.orElseThrow(IllegalArgumentException::new);
}
public static String adminLogin() {
return "admin";
}
public static String normalLogin() {
return "normal";
}
public static String userToken(String login) {
final String chain = login + ":" + login + "pass";
return Base64.getEncoder().encodeToString(chain.getBytes());
}
}
package es.uvigo.esei.daa.entities;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
public class PersonUnitTest {
@Test
public void testPersonIntStringString() {
final int id = 1;
final String name = "John";
final String surname = "Doe";
final Person person = new Person(id, name, surname);
assertThat(person.getId(), is(equalTo(id)));
assertThat(person.getName(), is(equalTo(name)));
assertThat(person.getSurname(), is(equalTo(surname)));
}
@Test(expected = NullPointerException.class)
public void testPersonIntStringStringNullName() {
new Person(1, null, "Doe");
}
@Test(expected = NullPointerException.class)
public void testPersonIntStringStringNullSurname() {
new Person(1, "John", null);
}
@Test
public void testSetName() {
final int id = 1;
final String surname = "Doe";
final Person person = new Person(id, "John", surname);
person.setName("Juan");
assertThat(person.getId(), is(equalTo(id)));
assertThat(person.getName(), is(equalTo("Juan")));
assertThat(person.getSurname(), is(equalTo(surname)));
}
@Test(expected = NullPointerException.class)
public void testSetNullName() {
final Person person = new Person(1, "John", "Doe");
person.setName(null);
}
@Test
public void testSetSurname() {
final int id = 1;
final String name = "John";
final Person person = new Person(id, name, "Doe");
person.setSurname("Dolores");
assertThat(person.getId(), is(equalTo(id)));
assertThat(person.getName(), is(equalTo(name)));
assertThat(person.getSurname(), is(equalTo("Dolores")));
}
@Test(expected = NullPointerException.class)
public void testSetNullSurname() {
final Person person = new Person(1, "John", "Doe");
person.setSurname(null);
}
@Test
public void testEqualsObject() {
final Person personA = new Person(1, "Name A", "Surname A");
final Person personB = new Person(1, "Name B", "Surname B");
assertTrue(personA.equals(personB));
}
@Test
public void testEqualsHashcode() {
EqualsVerifier.forClass(Person.class)
.withIgnoredFields("name", "surname")
.suppress(Warning.STRICT_INHERITANCE)
.suppress(Warning.NONFINAL_FIELDS)
.verify();
}
}
package es.uvigo.esei.daa.filters;
import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.UsersDAO;
import es.uvigo.esei.daa.entities.User;
/**
* This performs the Basic HTTP authentication following (almost) the same
* rules as the defined in the web.xml file.
*
* @author Miguel Reboiro Jato
*/
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
// Add here the list of REST paths that an administrator can access.
private final static List<String> ADMIN_PATHS = Arrays.asList("people");
private final UsersDAO dao;
public AuthorizationFilter() {
this.dao = new UsersDAO();
}
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the authentication passed in HTTP headers parameters
final String auth = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (auth == null) {
requestContext.abortWith(createResponse());
} else {
final byte[] decodedToken = Base64.getDecoder()
.decode(auth.substring(6));
final String userColonPass = new String(decodedToken);
final String[] userPass = userColonPass.split(":", 2);
if (userPass.length == 2) {
try {
if (this.dao.checkLogin(userPass[0], userPass[1])) {
final User user = this.dao.get(userPass[0]);
if (isAdminPath(requestContext) && !user.getRole().equals("ADMIN")) {
requestContext.abortWith(createResponse());
} else {
requestContext.setSecurityContext(new UserSecurityContext(user));
}
} else {
requestContext.abortWith(createResponse());
}
} catch (DAOException e) {
requestContext.abortWith(createResponse());
}
} else {
requestContext.abortWith(createResponse());
}
}
}
private static boolean isAdminPath(ContainerRequestContext context) {
final List<PathSegment> pathSegments = context.getUriInfo().getPathSegments();
if (pathSegments.isEmpty()) {
return false;
} else {
final String path = pathSegments.get(0).getPath();
return ADMIN_PATHS.contains(path);
}
}
private static Response createResponse() {
return Response.status(Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"DAAExample\"")
.entity("Page requires login.")
.build();
}
private static final class UserSecurityContext implements SecurityContext {
private final User user;
private UserSecurityContext(User user) {
this.user = user;
}
@Override
public boolean isUserInRole(String role) {
return user.getRole().equals(role);
}
@Override
public boolean isSecure() {
return false;
}
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return user.getLogin();
}
};
}
@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
}
}
package es.uvigo.esei.daa.listeners;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ApplicationContextBindings.class)
public @interface ApplicationContextBinding {
public String jndiUrl();
public String name() default "";
public Class<?> type() default None.class;
public final static class None {}
}
package es.uvigo.esei.daa.listeners;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContextBindings {
public ApplicationContextBinding[] value();
}
package es.uvigo.esei.daa.listeners;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import es.uvigo.esei.daa.listeners.ApplicationContextBinding.None;
public class ApplicationContextJndiBindingTestExecutionListener extends AbstractTestExecutionListener {
private SimpleNamingContextBuilder contextBuilder;
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
final Class<?> testClass = testContext.getTestClass();
final ApplicationContextBinding[] bindings = testClass.getAnnotationsByType(ApplicationContextBinding.class);
this.contextBuilder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
for (ApplicationContextBinding binding : bindings) {
final String bindingName = binding.name();
final Class<?> bindingType = binding.type();
Object bean;
if (bindingName.isEmpty() && bindingType.equals(None.class)) {
throw new IllegalArgumentException("name or type attributes must be configured in ApplicationContextBinding");
} else if (bindingName.isEmpty()) {
bean = testContext.getApplicationContext().getBean(bindingType);
} else if (bindingType.equals(None.class)) {
bean = testContext.getApplicationContext().getBean(bindingName);
} else {
bean = testContext.getApplicationContext().getBean(bindingName, bindingType);
}
this.contextBuilder.bind(binding.jndiUrl(), bean);
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
this.contextBuilder.clear();
this.contextBuilder = null;
}
}
package es.uvigo.esei.daa.listeners;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbManagement {
public String[] create() default "";
public String[] drop() default "";
public DbManagementAction action() default DbManagementAction.CREATE_DROP;
}
package es.uvigo.esei.daa.listeners;
public enum DbManagementAction {
DROP_CREATE_DROP, CREATE_DROP, ONLY_CREATE, ONLY_DROP;
}
package es.uvigo.esei.daa.listeners;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class DbManagementTestExecutionListener extends AbstractTestExecutionListener {
private DbManagement configuration;
private DataSource datasource;
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
final Class<?> testClass = testContext.getTestClass();
this.configuration = testClass.getAnnotation(DbManagement.class);
if (this.configuration == null)
throw new IllegalStateException(String.format(
"Missing %s annotation in %s class",
DbManagement.class.getSimpleName(), testClass.getName()
));
this.datasource = testContext.getApplicationContext().getBean(DataSource.class);
switch (this.configuration.action()) {
case DROP_CREATE_DROP:
executeDrop();
case CREATE_DROP:
case ONLY_CREATE:
executeCreate();
break;
default:
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
try {
switch (this.configuration.action()) {
case DROP_CREATE_DROP:
case CREATE_DROP:
case ONLY_DROP:
executeDrop();
break;
default:
}
} finally {
this.configuration = null;
this.datasource = null;
}
}
private void executeCreate() throws SQLException, IOException {
this.executeQueries(configuration.create());
}
private void executeDrop() throws SQLException, IOException {
this.executeQueries(configuration.drop());
}
private void executeQueries(String ... queriesPaths)
throws SQLException, IOException {
try (Connection connection = this.datasource.getConnection()) {
try (Statement statement = connection.createStatement()) {
for (String queryPath : queriesPaths) {
final String queries = readFile(queryPath);
for (String query : queries.split(";")) {
query = query.trim();
if (!query.trim().isEmpty()) {
statement.addBatch(query);
}
}
}
statement.executeBatch();
}
}
}
private static String readFile(String path) throws IOException {
final String classpathPrefix = "classpath:";
if (path.startsWith(classpathPrefix)) {
path = path.substring(classpathPrefix.length());
final ClassLoader classLoader =
DbManagementTestExecutionListener.class.getClassLoader();
final InputStream fileIS = classLoader.getResourceAsStream(path);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(fileIS))) {
final StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
} else {
return new String(Files.readAllBytes(Paths.get(path)));
}
}
}
/*
* #%L
* PanDrugsDB Backend
* %%
* Copyright (C) 2015 Fátima Al-Shahrour, Elena Piñeiro, Daniel Glez-Peña and Miguel Reboiro-Jato
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
package es.uvigo.esei.daa.matchers;
import static java.util.Objects.requireNonNull;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.StatusType;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class HasHttpStatus extends TypeSafeMatcher<Response> {
private final StatusType expectedStatus;
public HasHttpStatus(StatusType expectedStatus) {
this.expectedStatus = requireNonNull(expectedStatus);
}
public HasHttpStatus(int expectedStatus) {
this(Status.fromStatusCode(expectedStatus));
}
@Override
public void describeTo(Description description) {
description.appendValue(this.expectedStatus);
}
@Override
protected void describeMismatchSafely(Response item, Description mismatchDescription) {
mismatchDescription.appendText("was ").appendValue(item.getStatusInfo());
}
@Override
protected boolean matchesSafely(Response item) {
return item != null && expectedStatus.getStatusCode() == item.getStatusInfo().getStatusCode();
}
@Factory
public static Matcher<Response> hasHttpStatus(StatusType expectedStatus) {
return new HasHttpStatus(expectedStatus);
}
@Factory
public static Matcher<Response> hasHttpStatus(int expectedStatus) {
return new HasHttpStatus(expectedStatus);
}
@Factory
public static Matcher<Response> hasOkStatus() {
return new HasHttpStatus(Response.Status.OK);
}
@Factory
public static Matcher<Response> hasBadRequestStatus() {
return new HasHttpStatus(Response.Status.BAD_REQUEST);
}
@Factory
public static Matcher<Response> hasInternalServerErrorStatus() {
return new HasHttpStatus(Response.Status.INTERNAL_SERVER_ERROR);
}
@Factory
public static Matcher<Response> hasUnauthorized() {
return new HasHttpStatus(Response.Status.UNAUTHORIZED);
}
@Factory
public static Matcher<Response> hasForbidden() {
return new HasHttpStatus(Response.Status.FORBIDDEN);
}
}
package es.uvigo.esei.daa.matchers;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import es.uvigo.esei.daa.entities.Person;
public class IsEqualToPerson extends IsEqualToEntity<Person> {
public IsEqualToPerson(Person entity) {
super(entity);
}
@Override
protected boolean matchesSafely(Person actual) {
this.clearDescribeTo();
if (actual == null) {
this.addTemplatedDescription("actual", expected.toString());
return false;
} else {
return checkAttribute("id", Person::getId, actual)
&& checkAttribute("name", Person::getName, actual)
&& checkAttribute("surname", Person::getSurname, actual);
}
}
/**
* Factory method that creates a new {@link IsEqualToEntity} matcher with
* the provided {@link Person} as the expected value.
*
* @param person the expected person.
* @return a new {@link IsEqualToEntity} matcher with the provided
* {@link Person} as the expected value.
*/
@Factory
public static IsEqualToPerson equalsToPerson(Person person) {
return new IsEqualToPerson(person);
}
/**
* Factory method that returns a new {@link Matcher} that includes several
* {@link IsEqualToPerson} matchers, each one using an {@link Person} of the
* provided ones as the expected value.
*
* @param persons the persons to be used as the expected values.
* @return a new {@link Matcher} that includes several
* {@link IsEqualToPerson} matchers, each one using an {@link Person} of the
* provided ones as the expected value.
* @see IsEqualToEntity#containsEntityInAnyOrder(java.util.function.Function, Object...)
*/
@Factory
public static Matcher<Iterable<? extends Person>> containsPeopleInAnyOrder(Person ... persons) {
return containsEntityInAnyOrder(IsEqualToPerson::equalsToPerson, persons);
}
}
package es.uvigo.esei.daa.matchers;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import es.uvigo.esei.daa.entities.Person;
import es.uvigo.esei.daa.entities.User;
public class IsEqualToUser extends IsEqualToEntity<User> {
public IsEqualToUser(User entity) {
super(entity);
}
@Override
protected boolean matchesSafely(User actual) {
this.clearDescribeTo();
if (actual == null) {
this.addTemplatedDescription("actual", expected.toString());
return false;
} else {
return checkAttribute("login", User::getLogin, actual)
&& checkAttribute("password", User::getPassword, actual);
}
}
/**
* Factory method that creates a new {@link IsEqualToEntity} matcher with
* the provided {@link Person} as the expected value.
*
* @param user the expected person.
* @return a new {@link IsEqualToEntity} matcher with the provided
* {@link Person} as the expected value.
*/
@Factory
public static IsEqualToUser equalsToUser(User user) {
return new IsEqualToUser(user);
}
/**
* Factory method that returns a new {@link Matcher} that includes several
* {@link IsEqualToUser} matchers, each one using an {@link Person} of the
* provided ones as the expected value.
*
* @param users the persons to be used as the expected values.
* @return a new {@link Matcher} that includes several
* {@link IsEqualToUser} matchers, each one using an {@link Person} of the
* provided ones as the expected value.
* @see IsEqualToEntity#containsEntityInAnyOrder(java.util.function.Function, Object...)
*/
@Factory
public static Matcher<Iterable<? extends User>> containsPeopleInAnyOrder(User ... users) {
return containsEntityInAnyOrder(IsEqualToUser::equalsToUser, users);
}
}
package es.uvigo.esei.daa.rest;
import static es.uvigo.esei.daa.dataset.UsersDataset.adminLogin;
import static es.uvigo.esei.daa.dataset.UsersDataset.normalLogin;
import static es.uvigo.esei.daa.dataset.UsersDataset.user;
import static es.uvigo.esei.daa.dataset.UsersDataset.userToken;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized;
import static es.uvigo.esei.daa.matchers.IsEqualToUser.equalsToUser;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.IOException;
import javax.sql.DataSource;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import es.uvigo.esei.daa.DAAExampleTestApplication;
import es.uvigo.esei.daa.entities.User;
import es.uvigo.esei.daa.listeners.ApplicationContextBinding;
import es.uvigo.esei.daa.listeners.ApplicationContextJndiBindingTestExecutionListener;
import es.uvigo.esei.daa.listeners.DbManagement;
import es.uvigo.esei.daa.listeners.DbManagementTestExecutionListener;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:contexts/mem-context.xml")
@TestExecutionListeners({
DbUnitTestExecutionListener.class,
DbManagementTestExecutionListener.class,
ApplicationContextJndiBindingTestExecutionListener.class
})
@ApplicationContextBinding(
jndiUrl = "java:/comp/env/jdbc/daaexample",
type = DataSource.class
)
@DbManagement(
create = "classpath:db/hsqldb.sql",
drop = "classpath:db/hsqldb-drop.sql"
)
@DatabaseSetup("/datasets/dataset.xml")
@ExpectedDatabase("/datasets/dataset.xml")
public class UsersResourceTest extends JerseyTest {
@Override
protected Application configure() {
return new DAAExampleTestApplication();
}
@Override
protected void configureClient(ClientConfig config) {
super.configureClient(config);
// Enables JSON transformation in client
config.register(JacksonJsonProvider.class);
config.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
}
@Test
public void testGetAdminOwnUser() throws IOException {
final String admin = adminLogin();
final Response response = target("users/" + admin).request()
.header("Authorization", "Basic " + userToken(admin))
.get();
assertThat(response, hasOkStatus());
final User user = response.readEntity(User.class);
assertThat(user, is(equalsToUser(user(admin))));
}
@Test
public void testGetAdminOtherUser() throws IOException {
final String admin = adminLogin();
final String otherUser = normalLogin();
final Response response = target("users/" + otherUser).request()
.header("Authorization", "Basic " + userToken(admin))
.get();
assertThat(response, hasOkStatus());
final User user = response.readEntity(User.class);
assertThat(user, is(equalsToUser(user(otherUser))));
}
@Test
public void testGetNormalOwnUser() throws IOException {
final String login = normalLogin();
final Response response = target("users/" + login).request()
.header("Authorization", "Basic " + userToken(login))
.get();
assertThat(response, hasOkStatus());
final User user = response.readEntity(User.class);
assertThat(user, is(equalsToUser(user(login))));
}
@Test
public void testGetNoCredentials() throws IOException {
final Response response = target("users/" + normalLogin()).request().get();
assertThat(response, hasUnauthorized());
}
@Test
public void testGetBadCredentials() throws IOException {
final Response response = target("users/" + adminLogin()).request()
.header("Authorization", "Basic YmFkOmNyZWRlbnRpYWxz")
.get();
assertThat(response, hasUnauthorized());
}
@Test
public void testGetIllegalAccess() throws IOException {
final Response response = target("users/" + adminLogin()).request()
.header("Authorization", "Basic " + userToken(normalLogin()))
.get();
assertThat(response, hasUnauthorized());
}
}
package es.uvigo.esei.daa.suites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.rest.PeopleResourceTest;
import es.uvigo.esei.daa.rest.UsersResourceTest;
@SuiteClasses({
PeopleResourceTest.class,
UsersResourceTest.class
})
@RunWith(Suite.class)
public class IntegrationTestSuite {
}
package es.uvigo.esei.daa.suites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.entities.PersonUnitTest;
@SuiteClasses({
PersonUnitTest.class
})
@RunWith(Suite.class)
public class UnitTestSuite {
}
package es.uvigo.esei.daa.util;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
public final class ContextUtils {
private final static SimpleNamingContextBuilder CONTEXT_BUILDER =
new SimpleNamingContextBuilder();
private ContextUtils() {}
public static void createFakeContext(DataSource datasource)
throws IllegalStateException, NamingException {
CONTEXT_BUILDER.bind("java:/comp/env/jdbc/daaexample", datasource);
CONTEXT_BUILDER.activate();
}
public static void clearContextBuilder() {
CONTEXT_BUILDER.clear();
CONTEXT_BUILDER.deactivate();
}
}
package es.uvigo.esei.daa.util;
import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import java.sql.Connection;
import java.sql.ResultSet;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Before;
import com.mysql.jdbc.PreparedStatement;
/**
* Super-class for unit tests in the DAO layer.
*
* <p>The default {@link DatabaseQueryUnitTest#setUp()} method in this class
* create mocks for the datasource, connection, statement, and result variables
* that can be used by the DAO object under test.</p>
*
* @author Miguel Reboiro Jato
*/
public abstract class DatabaseQueryUnitTest {
protected DataSource datasource;
protected Connection connection;
protected PreparedStatement statement;
protected ResultSet result;
protected boolean verify;
/**
* Configures the mocks and enables the verification.
*
* @throws Exception if an error happens while configuring the mocks.
*/
@Before
public void setUp() throws Exception {
datasource = createMock(DataSource.class);
connection = createMock(Connection.class);
statement = createNiceMock(PreparedStatement.class);
result = createMock(ResultSet.class);
expect(datasource.getConnection())
.andReturn(connection);
expect(connection.prepareStatement(anyString()))
.andReturn(statement)
.anyTimes(); // statement is optional
expect(statement.executeQuery())
.andReturn(result)
.anyTimes(); // executeQuery is optional
statement.close();
connection.close();
verify = true;
}
/**
* Removes the default behavior of the mock instances and disables the mock
* verification.
*/
protected void resetAll() {
reset(result, statement, connection, datasource);
verify = false;
}
/**
* Replays the configured behavior of the mock instances and enables the
* mock verification. The mocked datasource is also added to a new context.
*/
protected void replayAll()
throws Exception {
replay(result, statement, connection, datasource);
verify = true;
ContextUtils.createFakeContext(datasource);
}
/**
* Clears the context and verifies the mocks if the verification is enabled.
*
* @throws Exception if an error happens during verification.
*/
@After
public void tearDown() throws Exception {
ContextUtils.clearContextBuilder();
try {
if (verify) {
verify(datasource, connection, statement, result);
verify = false;
}
} finally {
datasource = null;
connection = null;
statement = null;
result = null;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="org.hsqldb.jdbc.JDBCDriver" />
<property name="url" value="jdbc:hsqldb:hsql://localhost/daatestdb" />
<property name="username" value="sa" />
<property name="password" value="" />
<property name="maxTotal" value="8" />
<property name="maxIdle" value="4" />
<property name="maxWaitMillis" value="10000" />
</bean>
<bean id="sqlDataTypeFactory" class="org.dbunit.ext.hsqldb.HsqldbDataTypeFactory" />
<bean id="dbUnitDatabaseConfig" class="com.github.springtestdbunit.bean.DatabaseConfigBean">
<property name="datatypeFactory" ref="sqlDataTypeFactory" />
</bean>
<bean id="dbUnitDatabaseConnection"
class="com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean">
<property name="databaseConfig" ref="dbUnitDatabaseConfig" />
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="org.hsqldb.jdbc.JDBCDriver" />
<property name="url" value="jdbc:hsqldb:mem:daatestdb;create=true" />
<property name="username" value="daatestuser" />
<property name="password" value="daatestpass" />
<property name="maxTotal" value="8" />
<property name="maxIdle" value="4" />
<property name="maxWaitMillis" value="10000" />
</bean>
<bean id="sqlDataTypeFactory" class="org.dbunit.ext.hsqldb.HsqldbDataTypeFactory" />
<bean id="dbUnitDatabaseConfig" class="com.github.springtestdbunit.bean.DatabaseConfigBean">
<property name="datatypeFactory" ref="sqlDataTypeFactory" />
</bean>
<bean id="dbUnitDatabaseConnection"
class="com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean">
<property name="databaseConfig" ref="dbUnitDatabaseConfig" />
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dataset SYSTEM "dataset.dtd">
<dataset>
<people id="1" name="Antón" surname="Álvarez" />
<people id="2" name="Ana" surname="Amargo" />
<people id="3" name="Manuel" surname="Martínez" />
<people id="4" name="María" surname="Márquez" />
<people id="5" name="Lorenzo" surname="López" />
<people id="6" name="Laura" surname="Laredo" />
<people id="7" name="Perico" surname="Palotes" />
<people id="8" name="Patricia" surname="Pérez" />
<people id="9" name="Julia" surname="Justa" />
<people id="10" name="Juan" surname="Jiménez" />
<people id="11" name="John" surname="Doe" />
<users login="admin" password="713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca" role="ADMIN"/>
<users login="normal" password="7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83" role="USER" />
</dataset>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dataset SYSTEM "dataset.dtd">
<dataset>
<people id="1" name="Antón" surname="Álvarez" />
<people id="2" name="Ana" surname="Amargo" />
<people id="3" name="Manuel" surname="Martínez" />
<people id="4" name="María" surname="Márquez" />
<people id="6" name="Laura" surname="Laredo" />
<people id="7" name="Perico" surname="Palotes" />
<people id="8" name="Patricia" surname="Pérez" />
<people id="9" name="Julia" surname="Justa" />
<people id="10" name="Juan" surname="Jiménez" />
<users login="admin" password="713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca" role="ADMIN"/>
<users login="normal" password="7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83" role="USER" />
</dataset>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dataset SYSTEM "dataset.dtd">
<dataset>
<people id="1" name="Antón" surname="Álvarez" />
<people id="2" name="Ana" surname="Amargo" />
<people id="3" name="Manuel" surname="Martínez" />
<people id="4" name="María" surname="Márquez" />
<people id="5" name="John" surname="Doe" />
<people id="6" name="Laura" surname="Laredo" />
<people id="7" name="Perico" surname="Palotes" />
<people id="8" name="Patricia" surname="Pérez" />
<people id="9" name="Julia" surname="Justa" />
<people id="10" name="Juan" surname="Jiménez" />
<users login="admin" password="713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca" role="ADMIN"/>
<users login="normal" password="7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83" role="USER" />
</dataset>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT dataset (people*, users*)>
<!ELEMENT people EMPTY>
<!ELEMENT users EMPTY>
<!ATTLIST people
id CDATA #IMPLIED
name CDATA #IMPLIED
surname CDATA #IMPLIED
>
<!ATTLIST users
login CDATA #IMPLIED
password CDATA #IMPLIED
role CDATA #IMPLIED
>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dataset SYSTEM "dataset.dtd">
<dataset>
<people id="1" name="Antón" surname="Álvarez" />
<people id="2" name="Ana" surname="Amargo" />
<people id="3" name="Manuel" surname="Martínez" />
<people id="4" name="María" surname="Márquez" />
<people id="5" name="Lorenzo" surname="López" />
<people id="6" name="Laura" surname="Laredo" />
<people id="7" name="Perico" surname="Palotes" />
<people id="8" name="Patricia" surname="Pérez" />
<people id="9" name="Julia" surname="Justa" />
<people id="10" name="Juan" surname="Jiménez" />
<users login="admin" password="713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca" role="ADMIN"/>
<users login="normal" password="7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83" role="USER" />
</dataset>
\ No newline at end of file
DROP TABLE People IF EXISTS;
DROP TABLE Users IF EXISTS;
CREATE TABLE people (
id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL,
name VARCHAR(50) NOT NULL,
surname VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE users (
login VARCHAR(100) NOT NULL,
password VARCHAR(64) NOT NULL,
role VARCHAR(5) NOT NULL,
PRIMARY KEY (login)
);
\ No newline at end of file
<Context>
<!-- maxActive: Maximum number of database connections in pool. Make sure
you configure your mysqld max_connections large enough to handle all of your
db connections. Set to -1 for no limit. -->
<!-- maxIdle: Maximum number of idle database connections to retain in pool.
Set to -1 for no limit. See also the DBCP documentation on this and the minEvictableIdleTimeMillis
configuration parameter. -->
<!-- maxWait: Maximum time to wait for a database connection to become available
in ms, in this example 10 seconds. An Exception is thrown if this timeout
is exceeded. Set to -1 to wait indefinitely. -->
<!-- username and password: MySQL username and password for database connections -->
<!-- driverClassName: Class name for the old mm.mysql JDBC driver is org.gjt.mm.mysql.Driver
- we recommend using Connector/J though. Class name for the official MySQL
Connector/J driver is com.mysql.jdbc.Driver. -->
<!-- url: The JDBC connection url for connecting to your MySQL database. -->
<Resource name="jdbc/daaexample"
auth="Container"
type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="daa" password="daa"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/daaexampletest"
/>
</Context>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="chrome://restclient/content/restclient.html" />
<title>add</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">add</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td>chrome://restclient/content/restclient.html</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Headers</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Custom Header</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>name=name</td>
<td>Cookie</td>
</tr>
<tr>
<td>type</td>
<td>name=value</td>
<td>token=bXJqYXRvOm1yamF0bw==</td>
</tr>
<tr>
<td>click</td>
<td>css=#modal-custom-header &gt; div.modal-footer &gt; input.btn.btn-inverse</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Headers</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Custom Header</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>name=name</td>
<td>Content-Type</td>
</tr>
<tr>
<td>type</td>
<td>name=value</td>
<td>application/x-www-form-urlencoded</td>
</tr>
<tr>
<td>click</td>
<td>css=#modal-custom-header &gt; div.modal-footer &gt; input.btn.btn-inverse</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=request-body</td>
<td>name=Xián&amp;surname=Ximénez</td>
</tr>
<tr>
<td>click</td>
<td>link=POST</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=request-url</td>
<td>http://localhost:9080/DAAExample/rest/people</td>
</tr>
<tr>
<td>click</td>
<td>id=request-button</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=×</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=span.header-value</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=span.header-value</td>
<td>200 OK</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=#response-body-raw &gt; pre</td>
<td></td>
</tr>
<tr>
<td>storeText</td>
<td>css=#response-body-raw &gt; pre</td>
<td>responseBody</td>
</tr>
<tr>
<td>echo</td>
<td>${responseBody}</td>
<td></td>
</tr>
<tr>
<td>assertEval</td>
<td>JSON.parse(storedVars['responseBody']).name</td>
<td>Xián</td>
</tr>
<tr>
<td>assertEval</td>
<td>JSON.parse(storedVars['responseBody']).surname</td>
<td>Ximénez</td>
</tr>
</tbody></table>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="chrome://restclient/content/restclient.html" />
<title>addNoName</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">addNoName</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td>chrome://restclient/content/restclient.html</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Headers</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Custom Header</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>name=name</td>
<td>Cookie</td>
</tr>
<tr>
<td>type</td>
<td>name=value</td>
<td>token=bXJqYXRvOm1yamF0bw==</td>
</tr>
<tr>
<td>click</td>
<td>css=#modal-custom-header &gt; div.modal-footer &gt; input.btn.btn-inverse</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Headers</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Custom Header</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>name=name</td>
<td>Content-Type</td>
</tr>
<tr>
<td>type</td>
<td>name=value</td>
<td>application/x-www-form-urlencoded</td>
</tr>
<tr>
<td>click</td>
<td>css=#modal-custom-header &gt; div.modal-footer &gt; input.btn.btn-inverse</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=request-body</td>
<td>surname=Ximénez</td>
</tr>
<tr>
<td>click</td>
<td>link=POST</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=request-url</td>
<td>http://localhost:9080/DAAExample/rest/people</td>
</tr>
<tr>
<td>click</td>
<td>id=request-button</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=×</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=span.header-value</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=span.header-value</td>
<td>400 Bad Request</td>
</tr>
</tbody></table>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="chrome://restclient/content/restclient.html" />
<title>addNoSurname</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">addNoSurname</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td>chrome://restclient/content/restclient.html</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Headers</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Custom Header</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>name=name</td>
<td>Cookie</td>
</tr>
<tr>
<td>type</td>
<td>name=value</td>
<td>token=bXJqYXRvOm1yamF0bw==</td>
</tr>
<tr>
<td>click</td>
<td>css=#modal-custom-header &gt; div.modal-footer &gt; input.btn.btn-inverse</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Headers</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Custom Header</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>name=name</td>
<td>Content-Type</td>
</tr>
<tr>
<td>type</td>
<td>name=value</td>
<td>application/x-www-form-urlencoded</td>
</tr>
<tr>
<td>click</td>
<td>css=#modal-custom-header &gt; div.modal-footer &gt; input.btn.btn-inverse</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=request-body</td>
<td>name=Xián</td>
</tr>
<tr>
<td>click</td>
<td>link=POST</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>id=request-url</td>
<td>http://localhost:9080/DAAExample/rest/people</td>
</tr>
<tr>
<td>click</td>
<td>id=request-button</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=×</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=span.header-value</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=span.header-value</td>
<td>400 Bad Request</td>
</tr>
</tbody></table>
</body>
</html>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
<title>Test Suite</title>
</head>
<body>
<table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"><tbody>
<tr><td><b>Test Suite</b></td></tr>
<tr><td><a href="list.html">list</a></td></tr>
<tr><td><a href="add.html">add</a></td></tr>
<tr><td><a href="addNoName.html">addNoName</a></td></tr>
<tr><td><a href="addNoSurname.html">addNoSurname</a></td></tr>
<tr><td><a href="modify.html">modify</a></td></tr>
<tr><td><a href="modifyInvalidId.html">modifyInvalidId</a></td></tr>
<tr><td><a href="modifyNoId.html">modifyNoId</a></td></tr>
<tr><td><a href="modifyNoName.html">modifyNoName</a></td></tr>
<tr><td><a href="modifyNoSurname.html">modifyNoSurname</a></td></tr>
<tr><td><a href="delete.html">delete</a></td></tr>
<tr><td><a href="deleteInvalidId.html">deleteInvalidId</a></td></tr>
</tbody></table>
</body>
</html>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
<title>Test Suite</title>
</head>
<body>
<table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"><tbody>
<tr><td><b>Test Suite</b></td></tr>
<tr><td><a href="list.html">list</a></td></tr>
<tr><td><a href="add.html">add</a></td></tr>
<tr><td><a href="edit.html">edit</a></td></tr>
<tr><td><a href="delete.html">delete</a></td></tr>
</tbody></table>
</body>
</html>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment