Commit bf91733b authored by Administrator's avatar Administrator

Refactorizes the whole project for a simpler testing and execution

A big refactorization has been done in this commit, focused on
simplifying the testing and execution process. The Spring-DbUnit library
has been added to allow the use of DbUnit through annotations in the
test classes. This change has been complemented with two new test
listeners that allow the creation of custom initial contexts and the use
of custom SQL scripts to create and destroy the database tables before
and after the test execution, respectively. In addition, all the tests
are executed now using a HSQLDB database.

The datasource configuration has been pulled out of the project, and now
it must be provided by the container.

The sample classes have been reviewed, refactorized and documented with
JavaDoc.

The POM file has been reworked to ease test and application execution
using maven. The new execution modes are:
	- The default execution includes unit, integration and acceptance (with
Selenium) test execution.
	- No acceptance tests: mvn -P -acceptance-tests-cargo <phase>
	- Run server for manual acceptance tests: mvn
-Dcargo.tomcat.start.skip=true -Dcargo.tomcat.run.skip=false
-DskipTests=true pre-integration-test 
	- Run server with MySQL database: mvn -P
run-tomcat-mysql,-acceptance-tests-cargo -DskipTests=true package
cargo:run
parent d83602b8
/bin # General
/target /bak
/assembly
# Eclipse
.project .project
.classpath .classpath
.settings .settings
# Maven
/bin
/target
/assembly
# Testing
/servers
C:\\nppdf32Log\\debuglog.txt
DAAExample DAAExample
========== ==========
Example application for the DAA Subject 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.
## Ejecución con Maven
La configuración de Maven ha sido preparada para permitir varios tipos de
ejecución. En concreto:
* La ejecución por defecto (p.ej. `mvn install`) incluye los tests de
unidad, integración y aceptación (con Selenium).
* Si no se desea ejecutar los tests de aceptación debe desactivarse el perfil
`acceptance-tests-cargo`. Por ejemplo: `mvn -P -acceptance-tests-cargo install`.
* Si se desea arrancar el servidor para ejecutar los tests de aceptación
manualmente (se usará una base de datos HSQL), se debe ejecutar el comando:
`mvn -Dcargo.tomcat.start.skip=true -Dcargo.tomcat.run.skip=false
-DskipTests=true pre-integration-test`
* Si se desea arrancar el servidor con la base de datos MySQL, debe utilizarse
el comando: `mvn -P run-tomcat-mysql,-acceptance-tests-cargo cargo:run`. Es
necesario que el proyecto se haya empaquetado antes (p.ej. `mvn package`). En el
directorio `db` del proyecto se pueden encontrar los scripts necesarios para
crear la base de datos en MySQL.
...@@ -9,7 +9,7 @@ CREATE TABLE `daaexample`.`people` ( ...@@ -9,7 +9,7 @@ CREATE TABLE `daaexample`.`people` (
CREATE TABLE `daaexample`.`users` ( CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL, `login` varchar(100) NOT NULL,
`password` varbinary(64) DEFAULT NULL, `password` varchar(64) DEFAULT NULL,
PRIMARY KEY (`login`) PRIMARY KEY (`login`)
); );
...@@ -24,4 +24,4 @@ INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'María','Nu ...@@ -24,4 +24,4 @@ INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'María','Nu
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Alba','Fernández'); 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'); INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Asunción','Jiménez');
INSERT INTO `daaexample`.`users` (`login`,`password`) VALUES ('mrjato', '59189332a4abf8ddf66fde068cad09eb563b4bd974f7663d97ff6852a7910a73'); INSERT INTO `daaexample`.`users` (`login`,`password`) VALUES ('admin', '0b893644f3b2097d004c58d585e784ac92dd1356d25158a298573ad54ab2d15d');
...@@ -9,7 +9,7 @@ CREATE TABLE `daaexample`.`people` ( ...@@ -9,7 +9,7 @@ CREATE TABLE `daaexample`.`people` (
CREATE TABLE `daaexample`.`users` ( CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL, `login` varchar(100) NOT NULL,
`password` varbinary(64) DEFAULT NULL, `password` varchar(64) DEFAULT NULL,
PRIMARY KEY (`login`) PRIMARY KEY (`login`)
); );
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<groupId>es.uvigo.esei.daa</groupId> <groupId>es.uvigo.esei.daa</groupId>
<artifactId>example</artifactId> <artifactId>example</artifactId>
<packaging>war</packaging> <packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version> <version>0.1.0-SNAPSHOT</version>
<name>DAA Example</name> <name>DAA Example</name>
<licenses> <licenses>
...@@ -16,35 +16,40 @@ ...@@ -16,35 +16,40 @@
</licenses> </licenses>
<properties> <properties>
<!-- General --> <!-- General configuration -->
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.servers.directory>${project.basedir}/servers</project.servers.directory>
<cargo.tomcat.start.skip>false</cargo.tomcat.start.skip>
<cargo.tomcat.run.skip>true</cargo.tomcat.run.skip>
<!-- Dependencies --> <!-- General dependencies -->
<jersey.version>2.15</jersey.version> <jersey.version>2.22.1</jersey.version>
<mysql.version>5.1.34</mysql.version> <java.servlet.version>3.1.0</java.servlet.version>
<commons.dbcp.version>1.4</commons.dbcp.version> <commons.dbcp.version>2.1.1</commons.dbcp.version>
<java.servlet.version>3.0.1</java.servlet.version> <slf4j-jdk14.version>1.7.16</slf4j-jdk14.version>
<!-- Tests dependencies -->
<junit.version>4.12</junit.version> <junit.version>4.12</junit.version>
<selenium.java.version>2.44.0</selenium.java.version> <easymock.version>3.4</easymock.version>
<spring.test.version>4.1.4.RELEASE</spring.test.version> <selenium.java.version>2.49.1</selenium.java.version>
<easymock.version>3.3.1</easymock.version> <spring.test.version>4.2.4.RELEASE</spring.test.version>
<dbunit.version>2.5.1</dbunit.version>
<spring-test-dbunit.version>1.2.1</spring-test-dbunit.version>
<hsqldb.version>2.3.3</hsqldb.version>
<mysql.version>5.1.38</mysql.version>
<!-- Plugins --> <!-- Plugins -->
<jacoco.version>0.7.2.201409121644</jacoco.version> <surefire.version>2.19.1</surefire.version>
<surefire.version>2.18.1</surefire.version> <failsafe.version>2.19.1</failsafe.version>
<maven.war.plugin.version>2.6</maven.war.plugin.version> <maven.war.plugin.version>2.6</maven.war.plugin.version>
<tomcat.maven.plugin.version>2.2</tomcat.maven.plugin.version> <tomcat.maven.plugin.version>2.2</tomcat.maven.plugin.version>
<jacoco.version>0.7.5.201505241946</jacoco.version>
<cargo-maven2-plugin.version>1.4.18</cargo-maven2-plugin.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
...@@ -52,6 +57,12 @@ ...@@ -52,6 +57,12 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.glassfish.jersey.media</groupId> <groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId> <artifactId>jersey-media-json-jackson</artifactId>
...@@ -59,9 +70,9 @@ ...@@ -59,9 +70,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>org.slf4j</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>slf4j-jdk14</artifactId>
<version>${mysql.version}</version> <version>${slf4j-jdk14.version}</version>
</dependency> </dependency>
<!-- Test Scope --> <!-- Test Scope -->
...@@ -72,6 +83,13 @@ ...@@ -72,6 +83,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${easymock.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId> <groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId> <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
...@@ -80,8 +98,8 @@ ...@@ -80,8 +98,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-dbcp</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp</artifactId> <artifactId>commons-dbcp2</artifactId>
<version>${commons.dbcp.version}</version> <version>${commons.dbcp.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
...@@ -100,84 +118,62 @@ ...@@ -100,84 +118,62 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.test.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.test.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.easymock</groupId> <groupId>org.dbunit</groupId>
<artifactId>easymock</artifactId> <artifactId>dbunit</artifactId>
<version>${easymock.version}</version> <version>${dbunit.version}</version>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>${spring-test-dbunit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies>
<reporting> <dependency>
<plugins> <groupId>org.hsqldb</groupId>
<plugin> <artifactId>hsqldb</artifactId>
<groupId>org.apache.maven.plugins</groupId> <version>${hsqldb.version}</version>
<artifactId>maven-surefire-report-plugin</artifactId> <scope>test</scope>
<version>${surefire.version}</version> </dependency>
<reportSets>
<reportSet> <dependency>
<id>integration-tests</id> <groupId>mysql</groupId>
<reports> <artifactId>mysql-connector-java</artifactId>
<report>failsafe-report-only</report> <version>${mysql.version}</version>
</reports> <scope>test</scope>
</reportSet> </dependency>
</reportSets> </dependencies>
</plugin>
</plugins>
</reporting>
<build> <build>
<finalName>DAAExample</finalName> <finalName>DAAExample</finalName>
<plugins> <plugins>
<!-- Build war -->
<plugin> <plugin>
<artifactId>maven-war-plugin</artifactId> <artifactId>maven-war-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<version>${maven.war.plugin.version}</version> <version>${maven.war.plugin.version}</version>
<configuration> <configuration>
<warName>DAAExample</warName> <warName>${project.finalName}</warName>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-prepare-agent-integration</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
</execution>
<execution>
<id>coverage-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>coverage-report-integration</id>
<phase>integration-test</phase>
<goals>
<goal>report-integration</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
...@@ -189,15 +185,14 @@ ...@@ -189,15 +185,14 @@
</includes> </includes>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId> <artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire.version}</version> <version>${failsafe.version}</version>
<configuration> <configuration>
<includes> <includes>
<includes>**/IntegrationTestSuite.java</includes> <includes>**/IntegrationTestSuite.java</includes>
<includes>**/AcceptanceTestSuite.java</includes>
</includes> </includes>
</configuration> </configuration>
<executions> <executions>
...@@ -224,80 +219,276 @@ ...@@ -224,80 +219,276 @@
<goal>report-only</goal> <goal>report-only</goal>
</goals> </goals>
</execution> </execution>
<execution>
<id>integration-test-report</id>
<phase>integration-test</phase>
<goals>
<goal>report-only</goal>
<goal>failsafe-report-only</goal>
</goals>
</execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.tomcat.maven</groupId> <groupId>org.jacoco</groupId>
<artifactId>tomcat7-maven-plugin</artifactId> <artifactId>jacoco-maven-plugin</artifactId>
<version>${tomcat.maven.plugin.version}</version> <version>${jacoco.version}</version>
<configuration> <configuration>
<port>9080</port> <rules/>
<path>/DAAExample</path>
<contextFile>src/test/webapp/META-INF/context.xml</contextFile>
<useTestClasspath>false</useTestClasspath>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<id>start-tomcat7</id> <id>default-prepare-agent</id>
<phase>pre-integration-test</phase>
<goals> <goals>
<goal>run</goal> <goal>prepare-agent</goal>
</goals> </goals>
<configuration>
<fork>true</fork>
</configuration>
</execution> </execution>
<execution> <execution>
<id>stop-tomcat7</id> <id>coverage-report</id>
<phase>post-integration-test</phase> <phase>test</phase>
<goals> <goals>
<goal>shutdown</goal> <goal>report</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.jacoco</groupId>
<artifactId>
jacoco-maven-plugin
</artifactId>
<versionRange>
[0.7.2.201409121644,)
</versionRange>
<goals>
<goal>prepare-agent</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build> </build>
<profiles>
<profile>
<id>acceptance-tests-cargo</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${failsafe.version}</version>
<configuration>
<includes>
<includes>**/IntegrationTestSuite.java</includes>
<includes>**/AcceptanceTestSuite.java</includes>
</includes>
</configuration>
<executions>
<execution>
<id>default-integration-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>${surefire.version}</version>
<executions>
<execution>
<id>integration-test-report</id>
<phase>integration-test</phase>
<goals>
<goal>report-only</goal>
<goal>failsafe-report-only</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>jacoco-agent</id>
<phase>pre-integration-test</phase>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>jacoco.agent.itArgLine</propertyName>
</configuration>
</execution>
<execution>
<id>jacoco-report</id>
<phase>post-integration-test</phase>
<goals>
<goal>dump</goal>
<goal>report</goal>
</goals>
<configuration>
<outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<!-- current version -->
<groupId>fr.avianey.mojo</groupId>
<artifactId>hsqldb-maven-plugin</artifactId>
<version>1.0.0</version>
<!--
default value for in memory jdbc:hsqldb:hsql://localhost/xdb
override only values you want to change
-->
<configuration>
<driver>org.hsqldb.jdbc.JDBCDriver</driver>
<path>mem:daatestdb</path>
<address>localhost</address>
<name>daatestdb</name>
<username>sa</username>
<password></password>
</configuration>
<!-- call start and stop -->
<executions>
<execution>
<id>start-hsqldb</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-hsqldb</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${cargo-maven2-plugin.version}</version>
<configuration>
<container>
<containerId>tomcat8x</containerId>
<zipUrlInstaller>
<url>http://ftp.cixug.es/apache/tomcat/tomcat-8/v8.0.32/bin/apache-tomcat-8.0.32.zip</url>
<downloadDir>${project.servers.directory}/downloads</downloadDir>
<extractDir>${project.servers.directory}/extracts</extractDir>
</zipUrlInstaller>
<dependencies>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
</dependencies>
</container>
<configuration>
<home>${project.build.directory}/catalina-base</home>
<properties>
<cargo.jvmargs>${jacoco.agent.itArgLine},output=tcpserver,port=6300 -Drunmode=TEST</cargo.jvmargs>
<cargo.servlet.port>9080</cargo.servlet.port>
<cargo.datasource.datasource.h2>
cargo.datasource.jndi=jdbc/daaexample|
cargo.datasource.driver=org.hsqldb.jdbc.JDBCDriver|
cargo.datasource.url=jdbc:hsqldb:hsql://localhost/daatestdb|
cargo.datasource.username=sa|
cargo.datasource.password=|
cargo.datasource.maxActive=8|
cargo.datasource.maxIdle=4|
cargo.datasource.maxWait=10000
</cargo.datasource.datasource.h2>
</properties>
</configuration>
</configuration>
<executions>
<execution>
<id>start-tomcat</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<skip>${cargo.tomcat.start.skip}</skip>
</configuration>
</execution>
<execution>
<id>run-tomcat</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<skip>${cargo.tomcat.run.skip}</skip>
</configuration>
</execution>
<execution>
<id>stop-tomcat</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>run-tomcat-mysql</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${cargo-maven2-plugin.version}</version>
<configuration>
<container>
<containerId>tomcat8x</containerId>
<zipUrlInstaller>
<url>http://ftp.cixug.es/apache/tomcat/tomcat-8/v8.0.32/bin/apache-tomcat-8.0.32.zip</url>
<downloadDir>${project.servers.directory}/downloads</downloadDir>
<extractDir>${project.servers.directory}/extracts</extractDir>
</zipUrlInstaller>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</container>
<configuration>
<home>${project.build.directory}/catalina-base</home>
<properties>
<cargo.servlet.port>9080</cargo.servlet.port>
<cargo.datasource.datasource.h2>
cargo.datasource.jndi=jdbc/daaexample|
cargo.datasource.driver=com.mysql.jdbc.Driver|
cargo.datasource.url=jdbc:mysql://localhost/daaexample|
cargo.datasource.username=daa|
cargo.datasource.password=daa|
cargo.datasource.maxActive=8|
cargo.datasource.maxIdle=4|
cargo.datasource.maxWait=10000
</cargo.datasource.datasource.h2>
</properties>
</configuration>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project> </project>
package es.uvigo.esei.daa; package es.uvigo.esei.daa;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.ApplicationPath; import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;
import es.uvigo.esei.daa.rest.PeopleResource; import es.uvigo.esei.daa.rest.PeopleResource;
/**
* 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/*") @ApplicationPath("/rest/*")
public class DAAExampleApplication extends Application { public class DAAExampleApplication extends Application {
@Override @Override
public Set<Class<?>> getClasses() { public Set<Class<?>> getClasses() {
return new HashSet<>(Arrays.asList( return Stream.of(PeopleResource.class)
PeopleResource.class .collect(Collectors.toSet());
));
} }
@Override @Override
......
package es.uvigo.esei.daa; package es.uvigo.esei.daa;
import static java.util.Objects.requireNonNull;
import java.io.IOException; import java.io.IOException;
import java.util.Base64;
import java.util.Optional; import java.util.Optional;
import javax.servlet.Filter; import javax.servlet.Filter;
...@@ -17,8 +20,22 @@ import javax.servlet.http.HttpServletResponse; ...@@ -17,8 +20,22 @@ import javax.servlet.http.HttpServletResponse;
import es.uvigo.esei.daa.dao.DAOException; import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.UsersDAO; import es.uvigo.esei.daa.dao.UsersDAO;
/**
* Security filter that implements a login protocol based on the HTTP Basic
* Authentication protocol. In this case, the login and password can be provided
* as plain request parameters or as a cookie named "token" that should contain
* both values in the same format as HTTP Basic Authentication
* ({@code base64(login + ":" + password)}).
*
* @author Miguel Reboiro Jato
*
*/
@WebFilter(urlPatterns = { "/*", "/logout" }) @WebFilter(urlPatterns = { "/*", "/logout" })
public class LoginFilter implements Filter { public class LoginFilter implements Filter {
private static final String REST_PATH = "/rest";
private static final String INDEX_PATH = "/index.html";
private static final String LOGOUT_PATH = "/logout";
@Override @Override
public void doFilter( public void doFilter(
ServletRequest request, ServletRequest request,
...@@ -47,17 +64,25 @@ public class LoginFilter implements Filter { ...@@ -47,17 +64,25 @@ public class LoginFilter implements Filter {
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} }
} }
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void destroy() {
}
private boolean isLogoutPath(HttpServletRequest request) { private boolean isLogoutPath(HttpServletRequest request) {
return request.getServletPath().equals("/logout"); return request.getServletPath().equals(LOGOUT_PATH);
} }
private boolean isIndexPath(HttpServletRequest request) { private boolean isIndexPath(HttpServletRequest request) {
return request.getServletPath().equals("/index.html"); return request.getServletPath().equals(INDEX_PATH);
} }
private boolean isRestPath(HttpServletRequest request) { private boolean isRestPath(HttpServletRequest request) {
return request.getServletPath().startsWith("/rest"); return request.getServletPath().startsWith(REST_PATH);
} }
private void redirectToIndex( private void redirectToIndex(
...@@ -92,14 +117,15 @@ public class LoginFilter implements Filter { ...@@ -92,14 +117,15 @@ public class LoginFilter implements Filter {
final String password = request.getParameter("password"); final String password = request.getParameter("password");
if (login != null && password != null) { if (login != null && password != null) {
final String token = new UsersDAO().checkLogin(login, password); final UsersDAO dao = new UsersDAO();
if (dao.checkLogin(login, password)) {
if (token == null) { final Credentials credentials = new Credentials(login, password);
return false;
} else { response.addCookie(new Cookie("token", credentials.toToken()));
response.addCookie(new Cookie("token", token));
return true; return true;
} else {
return false;
} }
} else { } else {
return false; return false;
...@@ -113,20 +139,56 @@ public class LoginFilter implements Filter { ...@@ -113,20 +139,56 @@ public class LoginFilter implements Filter {
for (Cookie cookie : cookies) { for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) { if ("token".equals(cookie.getName())) {
final String token = new UsersDAO().checkToken(cookie.getValue()); final Credentials credentials = new Credentials(cookie.getValue());
return token != null; final UsersDAO dao = new UsersDAO();
return dao.checkLogin(credentials.getLogin(), credentials.getPassword());
} }
} }
return false; return false;
} }
@Override private static class Credentials {
public void init(FilterConfig config) throws ServletException { private final String login;
} private final String password;
@Override public Credentials(String token) {
public void destroy() { final String decodedToken = decodeBase64(token);
final int colonIndex = decodedToken.indexOf(':');
if (colonIndex < 0 || colonIndex == decodedToken.length()-1) {
throw new IllegalArgumentException("Invalid token");
}
this.login = decodedToken.substring(0, colonIndex);
this.password = decodedToken.substring(colonIndex + 1);
}
public Credentials(String login, String password) {
this.login = requireNonNull(login, "Login can't be null");
this.password = requireNonNull(password, "Password can't be null");
}
public String getLogin() {
return login;
}
public String getPassword() {
return password;
}
public String toToken() {
return encodeBase64(this.login + ":" + this.password);
}
private final static String decodeBase64(String text) {
return new String(Base64.getDecoder().decode(text.getBytes()));
}
private final static String encodeBase64(String text) {
return Base64.getEncoder().encodeToString(text.getBytes());
}
} }
} }
...@@ -10,25 +10,41 @@ import javax.naming.InitialContext; ...@@ -10,25 +10,41 @@ import javax.naming.InitialContext;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.sql.DataSource; 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 { public abstract class DAO {
private final static Logger LOG = Logger.getLogger("DAO"); private final static Logger LOG = Logger.getLogger(DAO.class.getName());
private final static String JNDI_NAME = "java:/comp/env/jdbc/daaexample"; private final static String JNDI_NAME = "java:/comp/env/jdbc/daaexample";
private DataSource dataSource; private DataSource dataSource;
/**
* Constructs a new instance of {@link DAO}.
*/
public DAO() { public DAO() {
Context initContext; Context initContext;
try { try {
initContext = new InitialContext(); initContext = new InitialContext();
this.dataSource = (DataSource) initContext.lookup(
System.getProperty("db.jndi", JNDI_NAME) this.dataSource = (DataSource) initContext.lookup(JNDI_NAME);
);
} catch (NamingException e) { } catch (NamingException e) {
LOG.log(Level.SEVERE, "Error initializing DAO", e); LOG.log(Level.SEVERE, "Error initializing DAO", e);
throw new RuntimeException(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 { protected Connection getConnection() throws SQLException {
return this.dataSource.getConnection(); return this.dataSource.getConnection();
} }
......
package es.uvigo.esei.daa.dao; package es.uvigo.esei.daa.dao;
/**
* A general exception class for the DAO layer.
*
* @author Miguel Reboiro Jato
*/
public class DAOException extends Exception { public class DAOException extends Exception {
private static final long serialVersionUID = 1L; 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() { 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) { public DAOException(String message) {
super(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) { public DAOException(Throwable cause) {
super(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) { public DAOException(String message, Throwable cause) {
super(message, 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, public DAOException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) { boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace); super(message, cause, enableSuppression, writableStackTrace);
......
...@@ -12,9 +12,24 @@ import java.util.logging.Logger; ...@@ -12,9 +12,24 @@ import java.util.logging.Logger;
import es.uvigo.esei.daa.entities.Person; import es.uvigo.esei.daa.entities.Person;
/**
* DAO class for the {@link Person} entities.
*
* @author Miguel Reboiro Jato
*
*/
public class PeopleDAO extends DAO { public class PeopleDAO extends DAO {
private final static Logger LOG = Logger.getLogger("PeopleDAO"); 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) public Person get(int id)
throws DAOException, IllegalArgumentException { throws DAOException, IllegalArgumentException {
try (final Connection conn = this.getConnection()) { try (final Connection conn = this.getConnection()) {
...@@ -25,11 +40,7 @@ public class PeopleDAO extends DAO { ...@@ -25,11 +40,7 @@ public class PeopleDAO extends DAO {
try (final ResultSet result = statement.executeQuery()) { try (final ResultSet result = statement.executeQuery()) {
if (result.next()) { if (result.next()) {
return new Person( return rowToEntity(result);
result.getInt("id"),
result.getString("name"),
result.getString("surname")
);
} else { } else {
throw new IllegalArgumentException("Invalid id"); throw new IllegalArgumentException("Invalid id");
} }
...@@ -41,6 +52,12 @@ public class PeopleDAO extends DAO { ...@@ -41,6 +52,12 @@ public class PeopleDAO extends DAO {
} }
} }
/**
* 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 { public List<Person> list() throws DAOException {
try (final Connection conn = this.getConnection()) { try (final Connection conn = this.getConnection()) {
final String query = "SELECT * FROM people"; final String query = "SELECT * FROM people";
...@@ -50,11 +67,7 @@ public class PeopleDAO extends DAO { ...@@ -50,11 +67,7 @@ public class PeopleDAO extends DAO {
final List<Person> people = new LinkedList<>(); final List<Person> people = new LinkedList<>();
while (result.next()) { while (result.next()) {
people.add(new Person( people.add(rowToEntity(result));
result.getInt("id"),
result.getString("name"),
result.getString("surname")
));
} }
return people; return people;
...@@ -66,41 +79,73 @@ public class PeopleDAO extends DAO { ...@@ -66,41 +79,73 @@ public class PeopleDAO extends DAO {
} }
} }
public void delete(int id) /**
* 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 { throws DAOException, IllegalArgumentException {
try (final Connection conn = this.getConnection()) { if (name == null || surname == null) {
final String query = "DELETE FROM people WHERE id=?"; throw new IllegalArgumentException("name and surname can't be null");
}
try (Connection conn = this.getConnection()) {
final String query = "INSERT INTO people VALUES(null, ?, ?)";
try (final PreparedStatement statement = conn.prepareStatement(query)) { try (PreparedStatement statement = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, id); statement.setString(1, name);
statement.setString(2, surname);
if (statement.executeUpdate() != 1) { if (statement.executeUpdate() == 1) {
throw new IllegalArgumentException("Invalid id"); 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) { } catch (SQLException e) {
LOG.log(Level.SEVERE, "Error deleting a person", e); LOG.log(Level.SEVERE, "Error adding a person", e);
throw new DAOException(e); throw new DAOException(e);
} }
} }
public Person modify(int id, String name, String surname) /**
* 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 { throws DAOException, IllegalArgumentException {
if (name == null || surname == null) { if (person == null) {
throw new IllegalArgumentException("name and surname can't be null"); throw new IllegalArgumentException("person can't be null");
} }
try (Connection conn = this.getConnection()) { try (Connection conn = this.getConnection()) {
final String query = "UPDATE people SET name=?, surname=? WHERE id=?"; final String query = "UPDATE people SET name=?, surname=? WHERE id=?";
try (PreparedStatement statement = conn.prepareStatement(query)) { try (PreparedStatement statement = conn.prepareStatement(query)) {
statement.setString(1, name); statement.setString(1, person.getName());
statement.setString(2, surname); statement.setString(2, person.getSurname());
statement.setInt(3, id); statement.setInt(3, person.getId());
if (statement.executeUpdate() == 1) { if (statement.executeUpdate() != 1) {
return new Person(id, name, surname);
} else {
throw new IllegalArgumentException("name and surname can't be null"); throw new IllegalArgumentException("name and surname can't be null");
} }
} }
...@@ -110,36 +155,37 @@ public class PeopleDAO extends DAO { ...@@ -110,36 +155,37 @@ public class PeopleDAO extends DAO {
} }
} }
public Person add(String name, String surname) /**
* 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 { throws DAOException, IllegalArgumentException {
if (name == null || surname == null) { try (final Connection conn = this.getConnection()) {
throw new IllegalArgumentException("name and surname can't be null"); final String query = "DELETE FROM people WHERE id=?";
}
try (Connection conn = this.getConnection()) {
final String query = "INSERT INTO people VALUES(null, ?, ?)";
try (PreparedStatement statement = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { try (final PreparedStatement statement = conn.prepareStatement(query)) {
statement.setString(1, name); statement.setInt(1, id);
statement.setString(2, surname);
if (statement.executeUpdate() == 1) { if (statement.executeUpdate() != 1) {
try (ResultSet resultKeys = statement.getGeneratedKeys()) { throw new IllegalArgumentException("Invalid id");
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) { } catch (SQLException e) {
LOG.log(Level.SEVERE, "Error adding a person", e); LOG.log(Level.SEVERE, "Error deleting a person", e);
throw new DAOException(e); throw new DAOException(e);
} }
} }
private Person rowToEntity(ResultSet row) throws SQLException {
return new Person(
row.getInt("id"),
row.getString("name"),
row.getString("surname")
);
}
} }
...@@ -6,14 +6,33 @@ import java.sql.Connection; ...@@ -6,14 +6,33 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Base64;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/**
* DAO class for managing the users of the system.
*
* @author Miguel Reboiro Jato
*/
public class UsersDAO extends DAO { public class UsersDAO extends DAO {
private final static Logger LOG = Logger.getLogger("UsersDAO"); private final static Logger LOG = Logger.getLogger(UsersDAO.class.getName());
public String checkLogin(String login, String password) throws DAOException { private final static String SALT = "daaexample-";
/**
* 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 Connection conn = this.getConnection()) { try (final Connection conn = this.getConnection()) {
final String query = "SELECT password FROM users WHERE login=?"; final String query = "SELECT password FROM users WHERE login=?";
...@@ -23,15 +42,11 @@ public class UsersDAO extends DAO { ...@@ -23,15 +42,11 @@ public class UsersDAO extends DAO {
try (final ResultSet result = statement.executeQuery()) { try (final ResultSet result = statement.executeQuery()) {
if (result.next()) { if (result.next()) {
final String dbPassword = result.getString("password"); final String dbPassword = result.getString("password");
final String shaPassword = encodeSha256(password); final String shaPassword = encodeSha256(SALT + password);
if (shaPassword.equals(dbPassword)) { return shaPassword.equals(dbPassword);
return encodeBase64(login + ":" + password);
} else {
return null;
}
} else { } else {
return null; return false;
} }
} }
} }
...@@ -41,48 +56,6 @@ public class UsersDAO extends DAO { ...@@ -41,48 +56,6 @@ public class UsersDAO extends DAO {
} }
} }
public String checkToken(String token)
throws DAOException, IllegalArgumentException {
final String decodedToken = decodeBase64(token);
final int colonIndex = decodedToken.indexOf(':');
if (colonIndex < 0 || colonIndex == decodedToken.length()-1) {
throw new IllegalArgumentException("Invalid token");
}
final String login = decodedToken.substring(0, decodedToken.indexOf(':'));
final String password = encodeSha256(decodedToken.substring(decodedToken.indexOf(':') + 1));
try (final Connection conn = this.getConnection()) {
final String query = "SELECT password FROM users WHERE login=?";
try (final PreparedStatement statement = conn.prepareStatement(query)) {
statement.setString(1, login);
try (final ResultSet result = statement.executeQuery()) {
if (result.next()) {
final String dbPassword = result.getString("password");
return password.equals(dbPassword) ? login : null;
} else {
return null;
}
}
}
} catch (SQLException e) {
LOG.log(Level.SEVERE, "Error checking token", e);
throw new DAOException(e);
}
}
private final static String decodeBase64(String text) {
return new String(Base64.getDecoder().decode(text.getBytes()));
}
private final static String encodeBase64(String text) {
return Base64.getEncoder().encodeToString(text.getBytes());
}
private final static String encodeSha256(String text) { private final static String encodeSha256(String text) {
try { try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256"); final MessageDigest digest = MessageDigest.getInstance("SHA-256");
......
package es.uvigo.esei.daa.entities; 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 { public class Person {
private int id; private int id;
private String name; private String name;
private String surname; private String surname;
public Person() { // 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) { public Person(int id, String name, String surname) {
this.id = id; this.id = id;
this.name = name; this.setName(name);
this.surname = surname; this.setSurname(surname);
} }
/**
* Returns the identifier of the person.
*
* @return the identifier of the person.
*/
public int getId() { public int getId() {
return id; return id;
} }
public void setId(int id) { /**
this.id = id; * Returns the name of the person.
} *
* @return the name of the person.
*/
public String getName() { public String getName() {
return name; 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) { public void setName(String name) {
this.name = 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() { public String getSurname() {
return surname; 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) { public void setSurname(String surname) {
this.surname = surname; this.surname = requireNonNull(surname, "Surname can't be null");
} }
@Override @Override
...@@ -43,8 +80,6 @@ public class Person { ...@@ -43,8 +80,6 @@ public class Person {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + id; result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((surname == null) ? 0 : surname.hashCode());
return result; return result;
} }
...@@ -59,16 +94,6 @@ public class Person { ...@@ -59,16 +94,6 @@ public class Person {
Person other = (Person) obj; Person other = (Person) obj;
if (id != other.id) if (id != other.id)
return false; return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (surname == null) {
if (other.surname != null)
return false;
} else if (!surname.equals(other.surname))
return false;
return true; return true;
} }
} }
...@@ -16,69 +16,130 @@ import javax.ws.rs.core.Response; ...@@ -16,69 +16,130 @@ import javax.ws.rs.core.Response;
import es.uvigo.esei.daa.dao.DAOException; import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.PeopleDAO; 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") @Path("/people")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public class PeopleResource { public class PeopleResource {
private final static Logger LOG = Logger.getLogger("PeopleResource"); private final static Logger LOG = Logger.getLogger(PeopleResource.class.getName());
private final PeopleDAO dao; private final PeopleDAO dao;
/**
* Constructs a new instance of {@link PeopleResource}.
*/
public PeopleResource() { public PeopleResource() {
this(new PeopleDAO()); this(new PeopleDAO());
} }
// For testing purposes // Needed for testing purposes
PeopleResource(PeopleDAO dao) { PeopleResource(PeopleDAO dao) {
this.dao = dao; this.dao = dao;
} }
@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();
}
}
/**
* 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 @GET
@Path("/{id}") @Path("/{id}")
public Response get( public Response get(
@PathParam("id") int id @PathParam("id") int id
) { ) {
try { try {
return Response.ok(this.dao.get(id), MediaType.APPLICATION_JSON).build(); final Person person = this.dao.get(id);
return Response.ok(person).build();
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in get method", iae); LOG.log(Level.FINE, "Invalid person id in get method", iae);
return Response.status(Response.Status.BAD_REQUEST) return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage()).build(); .entity(iae.getMessage())
.build();
} catch (DAOException e) { } catch (DAOException e) {
LOG.log(Level.SEVERE, "Error getting a person", 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(); return Response.serverError().entity(e.getMessage()).build();
} }
} }
@DELETE /**
@Path("/{id}") * Creates a new person in the system.
public Response delete( *
@PathParam("id") int id * @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 { try {
this.dao.delete(id); final Person newPerson = this.dao.add(name, surname);
return Response.ok(id).build(); return Response.ok(newPerson).build();
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in delete method", iae); LOG.log(Level.FINE, "Invalid person id in add method", iae);
return Response.status(Response.Status.BAD_REQUEST) return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage()).build(); .entity(iae.getMessage())
.build();
} catch (DAOException e) { } catch (DAOException e) {
LOG.log(Level.SEVERE, "Error deleting a person", e); LOG.log(Level.SEVERE, "Error adding a person", e);
return Response.serverError().entity(e.getMessage()).build();
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 @PUT
@Path("/{id}") @Path("/{id}")
public Response modify( public Response modify(
...@@ -87,31 +148,64 @@ public class PeopleResource { ...@@ -87,31 +148,64 @@ public class PeopleResource {
@FormParam("surname") String surname @FormParam("surname") String surname
) { ) {
try { try {
return Response.ok(this.dao.modify(id, name, surname)).build(); 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) { } catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in modify method", iae); LOG.log(Level.FINE, "Invalid person id in modify method", iae);
return Response.status(Response.Status.BAD_REQUEST) return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage()).build(); .entity(iae.getMessage())
.build();
} catch (DAOException e) { } catch (DAOException e) {
LOG.log(Level.SEVERE, "Error modifying a person", e); LOG.log(Level.SEVERE, "Error modifying a person", e);
return Response.serverError().entity(e.getMessage()).build();
return Response.serverError()
.entity(e.getMessage())
.build();
} }
} }
@POST /**
public Response add( * Deletes a person from the system.
@FormParam("name") String name, *
@FormParam("surname") String surname * @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 { try {
return Response.ok(this.dao.add(name, surname)).build(); this.dao.delete(id);
return Response.ok(id).build();
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
LOG.log(Level.FINE, "Invalid person id in add method", iae); LOG.log(Level.FINE, "Invalid person id in delete method", iae);
return Response.status(Response.Status.BAD_REQUEST) return Response.status(Response.Status.BAD_REQUEST)
.entity(iae.getMessage()).build(); .entity(iae.getMessage())
.build();
} catch (DAOException e) { } catch (DAOException e) {
LOG.log(Level.SEVERE, "Error adding a person", e); LOG.log(Level.SEVERE, "Error deleting a person", e);
return Response.serverError().entity(e.getMessage()).build();
return Response.serverError()
.entity(e.getMessage())
.build();
} }
} }
} }
<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/daaexample"
/>
</Context>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
id="DAAExample" version="3.0"> http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>DAAExample</display-name> <display-name>DAAExample</display-name>
<welcome-file-list> <welcome-file-list>
<welcome-file>index.html</welcome-file> <welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list> </welcome-file-list>
<resource-ref>
<description>DAA Example DB Connection</description>
<res-ref-name>jdbc/daaexample</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app> </web-app>
\ No newline at end of file
...@@ -6,9 +6,15 @@ ...@@ -6,9 +6,15 @@
</head> </head>
<body> <body>
<form action="main.html" method="POST"> <form action="main.html" method="POST">
<div>Login: <input name="login" type="text"/></div> <div>
<div>Password: <input name="password" type="password"/></div> Login: <input name="login" type="text" />
<div><input type="submit" value="Login"/></div> </div>
<div>
Password: <input name="password" type="password" />
</div>
<div>
<input type="submit" value="Login" />
</div>
</form> </form>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -114,6 +114,8 @@ function appendToTable(person) { ...@@ -114,6 +114,8 @@ function appendToTable(person) {
} }
function initPeople() { function initPeople() {
// getScript permite importar otro script. En este caso, se importan las
// funciones de acceso a datos.
$.getScript('js/dao/people.js', function() { $.getScript('js/dao/people.js', function() {
listPeople(function(people) { listPeople(function(people) {
$.each(people, function(key, person) { $.each(people, function(key, person) {
...@@ -121,6 +123,8 @@ function initPeople() { ...@@ -121,6 +123,8 @@ function initPeople() {
}); });
}); });
// La acción por defecto de enviar formulario (submit) se sobreescribe
// para que el envío sea a través de AJAX
$(peopleFormQuery).submit(function(event) { $(peopleFormQuery).submit(function(event) {
var person = formToPerson(); var person = formToPerson();
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<a id="#logout" href="logout">Logout</a> <a id="#logout" href="logout">Logout</a>
</div> </div>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.js"></script> <script type="text/javascript" src="http://code.jquery.com/jquery-2.2.0.min.js"></script>
<script type="text/javascript" src="js/view/people.js"></script> <script type="text/javascript" src="js/view/people.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
......
...@@ -15,7 +15,6 @@ import javax.sql.DataSource; ...@@ -15,7 +15,6 @@ import javax.sql.DataSource;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import com.mysql.jdbc.PreparedStatement; import com.mysql.jdbc.PreparedStatement;
...@@ -26,12 +25,6 @@ public abstract class DatabaseQueryUnitTest { ...@@ -26,12 +25,6 @@ public abstract class DatabaseQueryUnitTest {
protected ResultSet result; protected ResultSet result;
protected boolean verify; protected boolean verify;
@BeforeClass
public static void setUpBeforeClass()
throws Exception {
TestUtils.clearContextBuilder();
}
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
......
...@@ -2,27 +2,17 @@ package es.uvigo.esei.daa; ...@@ -2,27 +2,17 @@ package es.uvigo.esei.daa;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.sql.DataSource; import javax.sql.DataSource;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.springframework.mock.jndi.SimpleNamingContextBuilder;
public final class TestUtils { public final class TestUtils {
private final static SimpleNamingContextBuilder CONTEXT_BUILDER private final static SimpleNamingContextBuilder CONTEXT_BUILDER =
= new SimpleNamingContextBuilder(); new SimpleNamingContextBuilder();
private TestUtils() {} private TestUtils() {}
public static void createFakeContext() throws NamingException {
createFakeContext(createTestingDataSource());
}
public static void createFakeContext(DataSource datasource) public static void createFakeContext(DataSource datasource)
throws IllegalStateException, NamingException { throws IllegalStateException, NamingException {
...@@ -35,61 +25,6 @@ public final class TestUtils { ...@@ -35,61 +25,6 @@ public final class TestUtils {
CONTEXT_BUILDER.deactivate(); CONTEXT_BUILDER.deactivate();
} }
public static BasicDataSource createTestingDataSource() {
final BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/daaexampletest?allowMultiQueries=true");
ds.setUsername("daa");
ds.setPassword("daa");
ds.setMaxActive(100);
ds.setMaxIdle(30);
ds.setMaxWait(10000);
return ds;
}
public static BasicDataSource createEmptyDataSource() {
return new BasicDataSource();
}
public static void clearTestDatabase() throws SQLException {
final String queries = new StringBuilder()
.append("DELETE FROM `people`;")
.append("DELETE FROM `users`;")
.toString();
final DataSource ds = createTestingDataSource();
try (Connection connection = ds.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.execute(queries);
}
}
}
public static void initTestDatabase() throws SQLException {
final String queries = new StringBuilder()
.append("ALTER TABLE `people` AUTO_INCREMENT = 1;")
.append("ALTER TABLE `users` AUTO_INCREMENT = 1;")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Antón', 'Álvarez');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Ana', 'Amargo');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Manuel', 'Martínez');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'María', 'Márquez');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Lorenzo', 'López');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Laura', 'Laredo');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Perico', 'Palotes');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Patricia', 'Pérez');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Juan', 'Jiménez');")
.append("INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0, 'Julia', 'Justa');")
.append("INSERT INTO `users` (`login`,`password`) VALUES ('mrjato', '59189332a4abf8ddf66fde068cad09eb563b4bd974f7663d97ff6852a7910a73');")
.toString();
final DataSource ds = createTestingDataSource();
try (Connection connection = ds.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.execute(queries);
}
}
}
public static void assertOkStatus(final Response response) { public static void assertOkStatus(final Response response) {
assertEquals("Unexpected status code", Response.Status.OK.getStatusCode(), response.getStatus()); assertEquals("Unexpected status code", Response.Status.OK.getStatusCode(), response.getStatus());
} }
......
...@@ -2,35 +2,50 @@ package es.uvigo.esei.daa.dao; ...@@ -2,35 +2,50 @@ package es.uvigo.esei.daa.dao;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import org.junit.After; import javax.sql.DataSource;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; 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 es.uvigo.esei.daa.TestUtils; import com.github.springtestdbunit.DbUnitTestExecutionListener;
import es.uvigo.esei.daa.entities.Person; import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import es.uvigo.esei.daa.entities.Person;
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 PeopleDAOTest { public class PeopleDAOTest {
private PeopleDAO dao; private PeopleDAO dao;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
TestUtils.createFakeContext();
TestUtils.clearTestDatabase();
}
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
TestUtils.initTestDatabase();
this.dao = new PeopleDAO(); this.dao = new PeopleDAO();
} }
@After
public void tearDown() throws Exception {
TestUtils.clearTestDatabase();
this.dao = null;
}
@Test @Test
public void testGet() throws DAOException { public void testGet() throws DAOException {
final Person person = this.dao.get(4); final Person person = this.dao.get(4);
...@@ -41,7 +56,7 @@ public class PeopleDAOTest { ...@@ -41,7 +56,7 @@ public class PeopleDAOTest {
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testGetInvalidId() throws DAOException { public void testGetNonExistentId() throws DAOException {
this.dao.get(100); this.dao.get(100);
} }
...@@ -51,6 +66,7 @@ public class PeopleDAOTest { ...@@ -51,6 +66,7 @@ public class PeopleDAOTest {
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-delete.xml")
public void testDelete() throws DAOException { public void testDelete() throws DAOException {
this.dao.delete(4); this.dao.delete(4);
...@@ -58,13 +74,14 @@ public class PeopleDAOTest { ...@@ -58,13 +74,14 @@ public class PeopleDAOTest {
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testDeleteInvalidId() throws DAOException { public void testDeleteNonExistentId() throws DAOException {
this.dao.delete(100); this.dao.delete(100);
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-modify.xml")
public void testModify() throws DAOException { public void testModify() throws DAOException {
this.dao.modify(5, "John", "Doe"); this.dao.modify(new Person(5, "John", "Doe"));
final Person person = this.dao.get(5); final Person person = this.dao.get(5);
...@@ -74,21 +91,17 @@ public class PeopleDAOTest { ...@@ -74,21 +91,17 @@ public class PeopleDAOTest {
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testModifyInvalidId() throws DAOException { public void testModifyNonExistentId() throws DAOException {
this.dao.modify(100, "John", "Doe"); this.dao.modify(new Person(100, "John", "Doe"));
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNullName() throws DAOException {
this.dao.modify(5, null, "Doe");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testModifyNullSurname() throws DAOException { public void testModifyNullPerson() throws DAOException {
this.dao.modify(5, "John", null); this.dao.modify(null);
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-add.xml")
public void testAdd() throws DAOException { public void testAdd() throws DAOException {
final Person person = this.dao.add("John", "Doe"); final Person person = this.dao.add("John", "Doe");
......
...@@ -221,33 +221,18 @@ public class PeopleDAOUnitTest extends DatabaseQueryUnitTest { ...@@ -221,33 +221,18 @@ public class PeopleDAOUnitTest extends DatabaseQueryUnitTest {
replayAll(); replayAll();
final PeopleDAO peopleDAO = new PeopleDAO(); final PeopleDAO peopleDAO = new PeopleDAO();
final Person newPerson = peopleDAO.modify( peopleDAO.modify(person);
person.getId(), person.getName(), person.getSurname()
);
assertEquals(person, newPerson);
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNullName() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.modify(1, null, "Pepe");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testModifyNullSurname() throws Exception { public void testModifyNullPerson() throws Exception {
replayAll(); replayAll();
final PeopleDAO peopleDAO = new PeopleDAO(); final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations resetAll(); // No expectations
peopleDAO.modify(1, "Pepe", null); peopleDAO.modify(null);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
...@@ -257,7 +242,7 @@ public class PeopleDAOUnitTest extends DatabaseQueryUnitTest { ...@@ -257,7 +242,7 @@ public class PeopleDAOUnitTest extends DatabaseQueryUnitTest {
replayAll(); replayAll();
final PeopleDAO peopleDAO = new PeopleDAO(); final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(1, "Paco", "Pérez"); peopleDAO.modify(new Person(1, "Paco", "Pérez"));
} }
@Test(expected = DAOException.class) @Test(expected = DAOException.class)
...@@ -267,6 +252,6 @@ public class PeopleDAOUnitTest extends DatabaseQueryUnitTest { ...@@ -267,6 +252,6 @@ public class PeopleDAOUnitTest extends DatabaseQueryUnitTest {
replayAll(); replayAll();
final PeopleDAO peopleDAO = new PeopleDAO(); final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(1, "Paco", "Pérez"); peopleDAO.modify(new Person(1, "Paco", "Pérez"));
} }
} }
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)));
}
}
}
...@@ -2,11 +2,13 @@ package es.uvigo.esei.daa.rest; ...@@ -2,11 +2,13 @@ package es.uvigo.esei.daa.rest;
import static es.uvigo.esei.daa.TestUtils.assertBadRequestStatus; import static es.uvigo.esei.daa.TestUtils.assertBadRequestStatus;
import static es.uvigo.esei.daa.TestUtils.assertOkStatus; import static es.uvigo.esei.daa.TestUtils.assertOkStatus;
import static javax.ws.rs.client.Entity.entity;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import javax.sql.DataSource;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;
import javax.ws.rs.core.Form; import javax.ws.rs.core.Form;
...@@ -17,35 +19,41 @@ import javax.ws.rs.core.Response; ...@@ -17,35 +19,41 @@ import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.JerseyTest;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; 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.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.TestUtils;
import es.uvigo.esei.daa.entities.Person; import es.uvigo.esei.daa.entities.Person;
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 PeopleResourceTest extends JerseyTest { public class PeopleResourceTest extends JerseyTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
TestUtils.createFakeContext();
}
@Before
public void setUp() throws Exception {
super.setUp();
TestUtils.initTestDatabase();
}
@After
public void tearDown() throws Exception {
super.tearDown();
TestUtils.clearTestDatabase();
}
@Override @Override
protected Application configure() { protected Application configure() {
return new ResourceConfig(PeopleResource.class) return new ResourceConfig(PeopleResource.class)
...@@ -87,20 +95,21 @@ public class PeopleResourceTest extends JerseyTest { ...@@ -87,20 +95,21 @@ public class PeopleResourceTest extends JerseyTest {
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-add.xml")
public void testAdd() throws IOException { public void testAdd() throws IOException {
final Form form = new Form(); final Form form = new Form();
form.param("name", "Xoel"); form.param("name", "John");
form.param("surname", "Ximénez"); form.param("surname", "Doe");
final Response response = target("people") final Response response = target("people")
.request(MediaType.APPLICATION_JSON_TYPE) .request(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
assertOkStatus(response); assertOkStatus(response);
final Person person = response.readEntity(Person.class); final Person person = response.readEntity(Person.class);
assertEquals(11, person.getId()); assertEquals(11, person.getId());
assertEquals("Xoel", person.getName()); assertEquals("John", person.getName());
assertEquals("Ximénez", person.getSurname()); assertEquals("Doe", person.getSurname());
} }
@Test @Test
...@@ -110,7 +119,7 @@ public class PeopleResourceTest extends JerseyTest { ...@@ -110,7 +119,7 @@ public class PeopleResourceTest extends JerseyTest {
final Response response = target("people") final Response response = target("people")
.request(MediaType.APPLICATION_JSON_TYPE) .request(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
assertBadRequestStatus(response); assertBadRequestStatus(response);
} }
...@@ -122,26 +131,27 @@ public class PeopleResourceTest extends JerseyTest { ...@@ -122,26 +131,27 @@ public class PeopleResourceTest extends JerseyTest {
final Response response = target("people") final Response response = target("people")
.request(MediaType.APPLICATION_JSON_TYPE) .request(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
assertBadRequestStatus(response); assertBadRequestStatus(response);
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-modify.xml")
public void testModify() throws IOException { public void testModify() throws IOException {
final Form form = new Form(); final Form form = new Form();
form.param("name", "Marta"); form.param("name", "John");
form.param("surname", "Méndez"); form.param("surname", "Doe");
final Response response = target("people/4") final Response response = target("people/5")
.request(MediaType.APPLICATION_JSON_TYPE) .request(MediaType.APPLICATION_JSON_TYPE)
.put(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
assertOkStatus(response); assertOkStatus(response);
final Person person = response.readEntity(Person.class); final Person person = response.readEntity(Person.class);
assertEquals(4, person.getId()); assertEquals(5, person.getId());
assertEquals("Marta", person.getName()); assertEquals("John", person.getName());
assertEquals("Méndez", person.getSurname()); assertEquals("Doe", person.getSurname());
} }
@Test @Test
...@@ -151,7 +161,7 @@ public class PeopleResourceTest extends JerseyTest { ...@@ -151,7 +161,7 @@ public class PeopleResourceTest extends JerseyTest {
final Response response = target("people/4") final Response response = target("people/4")
.request(MediaType.APPLICATION_JSON_TYPE) .request(MediaType.APPLICATION_JSON_TYPE)
.put(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
assertBadRequestStatus(response); assertBadRequestStatus(response);
} }
...@@ -163,7 +173,7 @@ public class PeopleResourceTest extends JerseyTest { ...@@ -163,7 +173,7 @@ public class PeopleResourceTest extends JerseyTest {
final Response response = target("people/4") final Response response = target("people/4")
.request(MediaType.APPLICATION_JSON_TYPE) .request(MediaType.APPLICATION_JSON_TYPE)
.put(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
assertBadRequestStatus(response); assertBadRequestStatus(response);
} }
...@@ -182,6 +192,7 @@ public class PeopleResourceTest extends JerseyTest { ...@@ -182,6 +192,7 @@ public class PeopleResourceTest extends JerseyTest {
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-delete.xml")
public void testDelete() throws IOException { public void testDelete() throws IOException {
final Response response = target("people/4").request().delete(); final Response response = target("people/4").request().delete();
assertOkStatus(response); assertOkStatus(response);
......
package es.uvigo.esei.daa.rest; package es.uvigo.esei.daa.rest;
import static org.easymock.EasyMock.anyInt; import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyString; import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expect;
...@@ -131,21 +132,22 @@ public class PeopleResourceUnitTest { ...@@ -131,21 +132,22 @@ public class PeopleResourceUnitTest {
public void testModify() throws Exception { public void testModify() throws Exception {
final Person person = new Person(1, "Pepe", "Pérez"); final Person person = new Person(1, "Pepe", "Pérez");
expect(daoMock.modify(person.getId(), person.getName(), person.getSurname())) daoMock.modify(person);
.andReturn(person);
replay(daoMock);
replay(daoMock);
final Response response = resource.modify( final Response response = resource.modify(
person.getId(), person.getName(), person.getSurname()); person.getId(), person.getName(), person.getSurname());
assertEquals(person, response.getEntity()); assertEquals(person, response.getEntity());
assertEquals(Status.OK, response.getStatusInfo()); assertEquals(Status.OK, response.getStatusInfo());
} }
@Test @Test
public void testModifyDAOException() throws Exception { public void testModifyDAOException() throws Exception {
expect(daoMock.modify(anyInt(), anyString(), anyString())) daoMock.modify(anyObject());
.andThrow(new DAOException()); expectLastCall().andThrow(new DAOException());
replay(daoMock); replay(daoMock);
final Response response = resource.modify(1, "Paco", "Pérez"); final Response response = resource.modify(1, "Paco", "Pérez");
...@@ -154,8 +156,9 @@ public class PeopleResourceUnitTest { ...@@ -154,8 +156,9 @@ public class PeopleResourceUnitTest {
@Test @Test
public void testModifyIllegalArgumentException() throws Exception { public void testModifyIllegalArgumentException() throws Exception {
expect(daoMock.modify(anyInt(), anyString(), anyString())) daoMock.modify(anyObject());
.andThrow(new IllegalArgumentException()); expectLastCall().andThrow(new IllegalArgumentException());
replay(daoMock); replay(daoMock);
final Response response = resource.modify(1, "Paco", "Pérez"); final Response response = resource.modify(1, "Paco", "Pérez");
......
package es.uvigo.esei.daa.suites; package es.uvigo.esei.daa.suites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses; import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.web.PeopleWebTest; import es.uvigo.esei.daa.web.PeopleWebTest;
...@@ -7,6 +9,7 @@ import es.uvigo.esei.daa.web.PeopleWebTest; ...@@ -7,6 +9,7 @@ import es.uvigo.esei.daa.web.PeopleWebTest;
@SuiteClasses({ @SuiteClasses({
PeopleWebTest.class PeopleWebTest.class
}) })
@RunWith(Suite.class)
public class AcceptanceTestSuite { public class AcceptanceTestSuite {
} }
...@@ -6,12 +6,10 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -6,12 +6,10 @@ import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.dao.PeopleDAOTest; import es.uvigo.esei.daa.dao.PeopleDAOTest;
import es.uvigo.esei.daa.rest.PeopleResourceTest; import es.uvigo.esei.daa.rest.PeopleResourceTest;
import es.uvigo.esei.daa.web.PeopleWebTest;
@SuiteClasses({ @SuiteClasses({
PeopleDAOTest.class, PeopleDAOTest.class,
PeopleResourceTest.class, PeopleResourceTest.class
PeopleWebTest.class
}) })
@RunWith(Suite.class) @RunWith(Suite.class)
public class IntegrationTestSuite { public class IntegrationTestSuite {
......
package es.uvigo.esei.daa.web; package es.uvigo.esei.daa.web;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.assertFalse;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.openqa.selenium.By; import org.junit.runner.RunWith;
import org.openqa.selenium.Cookie; import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedConditions; import org.springframework.test.context.ContextConfiguration;
import org.openqa.selenium.support.ui.WebDriverWait; import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import es.uvigo.esei.daa.TestUtils; import es.uvigo.esei.daa.entities.Person;
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;
import es.uvigo.esei.daa.web.pages.MainPage;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:contexts/hsql-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 PeopleWebTest { public class PeopleWebTest {
private static final int DEFAULT_WAIT_TIME = 1; private static final int DEFAULT_WAIT_TIME = 1;
private WebDriver driver;
private StringBuffer verificationErrors = new StringBuffer();
@BeforeClass private WebDriver driver;
public static void setUpBeforeClass() throws Exception { private MainPage mainPage;
TestUtils.createFakeContext();
TestUtils.clearTestDatabase();
}
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
TestUtils.initTestDatabase();
final String baseUrl = "http://localhost:9080/DAAExample/"; final String baseUrl = "http://localhost:9080/DAAExample/";
driver = new FirefoxDriver(); driver = new FirefoxDriver();
driver.get(baseUrl); driver.get(baseUrl);
driver.manage().addCookie(new Cookie("token", "bXJqYXRvOm1yamF0bw=="));
// Login as "admin:admin"
driver.manage().addCookie(new Cookie("token", "YWRtaW46YWRtaW4="));
// Driver will wait DEFAULT_WAIT_TIME if it doesn't find and element. // Driver will wait DEFAULT_WAIT_TIME if it doesn't find and element.
driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS); driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
driver.get(baseUrl + "main.html"); mainPage = new MainPage(driver, baseUrl);
driver.findElement(By.id("people-list")); mainPage.navigateTo();
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
TestUtils.clearTestDatabase();
driver.quit(); driver.quit();
String verificationErrorString = verificationErrors.toString(); driver = null;
if (!"".equals(verificationErrorString)) { mainPage = null;
fail(verificationErrorString);
}
} }
@Test @Test
public void testList() throws Exception { public void testList() throws Exception {
verifyXpathCount("//tr", 11); assertEquals(10, mainPage.countPeople());
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-add.xml")
public void testAdd() throws Exception { public void testAdd() throws Exception {
final String name = "Hola"; final String name = "John";
final String surname = "Mundo"; final String surname = "Doe";
driver.findElement(By.name("name")).clear(); final Person newPerson = mainPage.addPerson(name, surname);
driver.findElement(By.name("name")).sendKeys(name);
driver.findElement(By.name("surname")).clear();
driver.findElement(By.name("surname")).sendKeys(surname);
driver.findElement(By.id("btnSubmit")).click();
driver.findElement(By.xpath("//td[text()='Hola']"));
assertEquals(name, assertEquals(name, newPerson.getName());
driver.findElement(By.cssSelector("tr:last-child > td.name")).getText() assertEquals(surname, newPerson.getSurname());
);
assertEquals(surname,
driver.findElement(By.cssSelector("tr:last-child > td.surname")).getText()
);
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-modify.xml")
public void testEdit() throws Exception { public void testEdit() throws Exception {
final String name = "Xián"; final int id = 5;
final String surname = "Ximénez"; final String newName = "John";
final String newSurname = "Doe";
mainPage.editPerson(id, "John", "Doe");
final String trId = driver.findElement(By.xpath("//tr[last()]")).getAttribute("id"); final Person person = mainPage.getPerson(id);
driver.findElement(By.xpath("//tr[@id='" + trId + "']//a[text()='Edit']")).click();
driver.findElement(By.name("name")).clear();
driver.findElement(By.name("name")).sendKeys(name);
driver.findElement(By.name("surname")).clear();
driver.findElement(By.name("surname")).sendKeys(surname);
driver.findElement(By.id("btnSubmit")).click();
waitForTextInElement(By.name("name"), "");
waitForTextInElement(By.name("surname"), "");
assertEquals(name, assertEquals(id, person.getId());
driver.findElement(By.xpath("//tr[@id='" + trId + "']/td[@class='name']")).getText() assertEquals(newName, person.getName());
); assertEquals(newSurname, person.getSurname());
assertEquals(surname,
driver.findElement(By.xpath("//tr[@id='" + trId + "']/td[@class='surname']")).getText()
);
} }
@Test @Test
@ExpectedDatabase("/datasets/dataset-delete.xml")
public void testDelete() throws Exception { public void testDelete() throws Exception {
final String trId = driver.findElement(By.xpath("//tr[last()]")).getAttribute("id"); mainPage.deletePerson(4);
driver.findElement(By.xpath("(//a[contains(text(),'Delete')])[last()]")).click();
driver.switchTo().alert().accept(); assertFalse(mainPage.hasPerson(4));
waitUntilNotFindElement(By.id(trId));
}
private boolean waitUntilNotFindElement(By by) {
return new WebDriverWait(driver, DEFAULT_WAIT_TIME)
.until(ExpectedConditions.invisibilityOfElementLocated(by));
}
private boolean waitForTextInElement(By by, String text) {
return new WebDriverWait(driver, DEFAULT_WAIT_TIME)
.until(ExpectedConditions.textToBePresentInElementLocated(by, text));
}
private void verifyXpathCount(String xpath, int count) {
try {
assertEquals(count, driver.findElements(By.xpath(xpath)).size());
} catch (Error e) {
verificationErrors.append(e.toString());
}
} }
} }
package es.uvigo.esei.daa.web.pages;
import static org.openqa.selenium.support.ui.ExpectedConditions.presenceOfElementLocated;
import static org.openqa.selenium.support.ui.ExpectedConditions.textToBePresentInElement;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import es.uvigo.esei.daa.entities.Person;
public class MainPage {
private static final String TABLE_ID = "people-list";
private static final String FORM_ID = "people-form";
private static final String ID_PREFIX = "person-";
private final WebDriver driver;
private final WebDriverWait wait;
private final String baseUrl;
public MainPage(WebDriver driver, String baseUrl) {
this.driver = driver;
this.baseUrl = baseUrl;
this.wait = new WebDriverWait(driver, 1);
}
public void navigateTo() {
this.driver.get(this.baseUrl + "main.html");
this.wait.until(presenceOfElementLocated(By.id("people-list")));
}
public int countPeople() {
return new PeopleTable(this.driver).countPeople();
}
public Person getLastPerson() {
return new PeopleTable(this.driver).getPersonInLastRow();
}
public Person getPerson(int id) {
return new PeopleTable(this.driver).getPersonById(id);
}
public boolean hasPerson(int id) {
return new PeopleTable(this.driver).hasPerson(id);
}
public Person addPerson(String name, String surname) {
final PersonForm form = new PersonForm(this.driver);
form.clear();
form.setName(name);
form.setSurname(surname);
form.submit();
final PeopleTable table = new PeopleTable(driver);
return table.getPerson(name, surname);
}
public void editPerson(int id, String newName, String newSurname) {
final PeopleTable table = new PeopleTable(this.driver);
table.editPerson(id);
final PersonForm form = new PersonForm(this.driver);
form.setName(newName);
form.setSurname(newSurname);
form.submit();
}
public void deletePerson(int id) {
final PeopleTable table = new PeopleTable(this.driver);
table.deletePerson(id);
}
private final static class PeopleTable {
private final WebDriver driver;
private final WebElement table;
public PeopleTable(WebDriver driver) {
this.driver = driver;
this.table = this.driver.findElement(By.id(TABLE_ID));
}
public boolean hasPerson(int id) {
try {
return this.getPersonRow(id) != null;
} catch (NoSuchElementException nsee) {
return false;
}
}
public void editPerson(int id) {
final WebElement personRow = this.getPersonRow(id);
personRow.findElement(By.className("edit")).click();
}
public void deletePerson(int id) {
final WebElement personRow = this.getPersonRow(id);
personRow.findElement(By.className("delete")).click();
this.acceptDialog();
}
public Person getPersonById(int id) {
return rowToPerson(getPersonRow(id));
}
public Person getPerson(String name, String surname) {
return rowToPerson(getPersonRow(name, surname));
}
public Person getPersonInLastRow() {
final WebElement row = this.table.findElement(By.cssSelector("tr:last-child"));
return rowToPerson(row);
}
private WebElement getPersonRow(int id) {
return this.table.findElement(By.id(ID_PREFIX + id));
}
public WebElement getPersonRow(String name, String surname) {
final String xpathQuery = String.format(
"//td[@class = 'name' and text() = '%s']"
+ "/following-sibling::td[@class = 'surname' and text() = '%s']"
+ "/parent::tr",
name, surname
);
return table.findElement(By.xpath(xpathQuery));
}
public int countPeople() {
final String xpathQuery = "//tr[starts-with(@id, '" + ID_PREFIX + "')]";
final List<WebElement> peopleRows =
this.table.findElements(By.xpath(xpathQuery));
return peopleRows.size();
}
private Person rowToPerson(WebElement row) {
return new Person(
Integer.parseInt(row.getAttribute("id").substring(ID_PREFIX.length())),
row.findElement(By.className("name")).getText(),
row.findElement(By.className("surname")).getText()
);
}
private void acceptDialog() {
driver.switchTo().alert().accept();
}
}
public final static class PersonForm {
private final WebDriverWait wait;
private final WebElement fieldName;
private final WebElement fieldSurname;
private final WebElement buttonClear;
private final WebElement buttonSubmit;
public PersonForm(WebDriver driver) {
this.wait = new WebDriverWait(driver, 1);
final WebElement form = driver.findElement(By.id(FORM_ID));
this.fieldName = form.findElement(By.name("name"));
this.fieldSurname = form.findElement(By.name("surname"));
this.buttonClear = form.findElement(By.id("btnClear"));
this.buttonSubmit = form.findElement(By.id("btnSubmit"));
}
public void submit() {
this.buttonSubmit.click();
this.waitForCleanFields();
}
public void clear() {
this.buttonClear.click();
this.waitForCleanFields();
}
public void setName(String name) {
this.fieldName.clear();
this.fieldName.sendKeys(name);
}
public void setSurname(String surname) {
this.fieldSurname.clear();
this.fieldSurname.sendKeys(surname);
}
public String getName() {
return this.fieldName.getText();
}
public String getSurname() {
return this.fieldSurname.getText();
}
private void waitForCleanFields() {
wait.until(textToBePresentInElement(fieldName, ""));
wait.until(textToBePresentInElement(fieldSurname, ""));
}
}
}
<?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.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="0b893644f3b2097d004c58d585e784ac92dd1356d25158a298573ad54ab2d15d" />
</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="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="0b893644f3b2097d004c58d585e784ac92dd1356d25158a298573ad54ab2d15d" />
</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="0b893644f3b2097d004c58d585e784ac92dd1356d25158a298573ad54ab2d15d" />
</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
>
<?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="0b893644f3b2097d004c58d585e784ac92dd1356d25158a298573ad54ab2d15d" />
</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,
PRIMARY KEY (login)
);
\ No newline at end of file
DELETE FROM `people`;
DELETE FROM `users`;
ALTER TABLE `people` AUTO_INCREMENT = 1;
ALTER TABLE `users` AUTO_INCREMENT = 1;
\ No newline at end of file
DROP TABLE IF EXISTS `people`;
CREATE TABLE `people` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`surname` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`login` varchar(100) NOT NULL,
`password` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`login`)
);
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Antón','Álvarez');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Ana','Amargo');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Manuel','Martínez');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'María','Márquez');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Lorenzo','López');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Laura','Laredo');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Perico','Palotes');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Patricia','Pérez');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Juan','Jiménez');
INSERT INTO `people` (`id`,`name`,`surname`) VALUES (0,'Julia','Justa');
-- login: mrjato, password: mrjato
INSERT INTO `users` (`login`,`password`) VALUES ('mrjato', '59189332a4abf8ddf66fde068cad09eb563b4bd974f7663d97ff6852a7910a73');
CREATE DATABASE `daaexampletest`;
CREATE TABLE `daaexampletest`.`people` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`surname` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `daaexampletest`.`users` (
`login` varchar(100) NOT NULL,
`password` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`login`)
);
GRANT ALL ON `daaexampletest`.* TO 'daa'@'localhost' IDENTIFIED BY 'daa';
\ No newline at end of file
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