diff --git a/src/main/angular/src/app/components/login-panel/login-panel.component.scss b/src/main/angular/src/app/components/login-panel/login-panel.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a5c2b92db7aeac3f9a8a7d7242c375cd636af6d3
--- /dev/null
+++ b/src/main/angular/src/app/components/login-panel/login-panel.component.scss
@@ -0,0 +1,65 @@
+/*!
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#container {
+ height: 100%;
+ width: 100%;
+ display: -ms-flexbox;
+ display: -webkit-box;
+ display: flex;
+ -ms-flex-align: center;
+ -ms-flex-pack: center;
+ -webkit-box-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ justify-content: center;
+ padding-top: 40px;
+ padding-bottom: 40px;
+}
+
+.form-signin {
+ width: 100%;
+ max-width: 330px;
+ padding: 15px;
+ margin: 0 auto;
+}
+
+.form-signin .form-control {
+ position: relative;
+ box-sizing: border-box;
+ height: auto;
+ padding: 10px;
+ font-size: 16px;
+}
+
+.form-signin .form-control:focus {
+ z-index: 2;
+}
+
+#login {
+ margin-bottom: -1px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+#password {
+ margin-bottom: 10px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
diff --git a/src/main/angular/src/app/components/login-panel/login-panel.component.spec.ts b/src/main/angular/src/app/components/login-panel/login-panel.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f5df86f55953e52afa04e88e7cda547a1812ca4a
--- /dev/null
+++ b/src/main/angular/src/app/components/login-panel/login-panel.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginPanelComponent } from './login-panel.component';
+
+describe('LoginPanelComponent', () => {
+ let component: LoginPanelComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ LoginPanelComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoginPanelComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/components/login-panel/login-panel.component.ts b/src/main/angular/src/app/components/login-panel/login-panel.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fce4c3b8495a89eff7f9fe29d713604e8bd39a12
--- /dev/null
+++ b/src/main/angular/src/app/components/login-panel/login-panel.component.ts
@@ -0,0 +1,52 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Component} from '@angular/core';
+import {Router} from '@angular/router';
+import {AuthenticationService} from '../../services/authentication.service';
+import {UserModel} from '../../models';
+
+@Component({
+ selector: 'app-login-panel',
+ templateUrl: './login-panel.component.html',
+ styleUrls: ['./login-panel.component.scss']
+})
+export class LoginPanelComponent {
+ public login: string;
+ public password: string;
+
+ public constructor(
+ private readonly authenticationService: AuthenticationService,
+ private readonly router: Router
+ ) {
+ this.login = '';
+ this.password = '';
+ }
+
+ public onLogin(): void {
+ const credentials: UserModel = {
+ login: this.login,
+ password: this.password
+ };
+
+ this.authenticationService.tryLogin(credentials)
+ .subscribe(() => this.router.navigate(['/']));
+ }
+
+}
diff --git a/src/main/angular/src/app/components/main-panel/main-panel.component.html b/src/main/angular/src/app/components/main-panel/main-panel.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..82dd45dcc2c3f2257bf02de92e8b8f9c72e472d9
--- /dev/null
+++ b/src/main/angular/src/app/components/main-panel/main-panel.component.html
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/src/main/angular/src/app/components/main-panel/main-panel.component.scss b/src/main/angular/src/app/components/main-panel/main-panel.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7c0661204d84fb41b3f44677c33556c7d8b191bb
--- /dev/null
+++ b/src/main/angular/src/app/components/main-panel/main-panel.component.scss
@@ -0,0 +1,18 @@
+/*!
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
diff --git a/src/main/angular/src/app/components/main-panel/main-panel.component.spec.ts b/src/main/angular/src/app/components/main-panel/main-panel.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c702937ae0a40477800393e9d26718953c877ed9
--- /dev/null
+++ b/src/main/angular/src/app/components/main-panel/main-panel.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MainPanelComponent } from './main-panel.component';
+
+describe('MainPanelComponent', () => {
+ let component: MainPanelComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ MainPanelComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MainPanelComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/components/main-panel/main-panel.component.ts b/src/main/angular/src/app/components/main-panel/main-panel.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..81a052f475bec70a14b06bbbb3f07dbc96191ebf
--- /dev/null
+++ b/src/main/angular/src/app/components/main-panel/main-panel.component.ts
@@ -0,0 +1,50 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Component} from '@angular/core';
+import {Observable} from 'rxjs';
+import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
+import {map} from 'rxjs/operators';
+import {Router} from '@angular/router';
+import {AuthenticationService} from '../../services/authentication.service';
+
+@Component({
+ selector: 'app-main-panel',
+ templateUrl: './main-panel.component.html',
+ styleUrls: ['./main-panel.component.scss']
+})
+export class MainPanelComponent {
+ public readonly isHandset$: Observable;
+
+ public constructor(
+ private readonly authenticationService: AuthenticationService,
+ private readonly breakpointObserver: BreakpointObserver,
+ private readonly router: Router
+ ) {
+ this.isHandset$ = this.breakpointObserver.observe(Breakpoints.Handset)
+ .pipe(
+ map(result => result.matches)
+ );
+ }
+
+ public onLogout(): void {
+ this.authenticationService.logout();
+ this.router.navigate(['welcome']);
+ }
+}
diff --git a/src/main/angular/src/app/guards/authenticated.guard.spec.ts b/src/main/angular/src/app/guards/authenticated.guard.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dcf91ef5ee85a4b8ec9658005036c98d4161990d
--- /dev/null
+++ b/src/main/angular/src/app/guards/authenticated.guard.spec.ts
@@ -0,0 +1,34 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {inject, TestBed} from '@angular/core/testing';
+
+import {AuthenticatedGuard} from './authenticated.guard';
+
+describe('AuthenticatedGuard', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [AuthenticatedGuard]
+ });
+ });
+
+ it('should ...', inject([AuthenticatedGuard], (guard: AuthenticatedGuard) => {
+ expect(guard).toBeTruthy();
+ }));
+});
diff --git a/src/main/angular/src/app/guards/authenticated.guard.ts b/src/main/angular/src/app/guards/authenticated.guard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b2b767ab8dc47fece35f0a73f41da9f86e74843a
--- /dev/null
+++ b/src/main/angular/src/app/guards/authenticated.guard.ts
@@ -0,0 +1,53 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
+import {Observable} from 'rxjs';
+import {map, tap} from 'rxjs/operators';
+import {AuthenticationService} from '../services/authentication.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthenticatedGuard implements CanActivate {
+ public constructor(
+ private readonly authenticationService: AuthenticationService,
+ private readonly router: Router
+ ) {}
+
+ public canActivate(
+ next: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot
+ ): boolean | Observable {
+ if (this.authenticationService.isLogged()) {
+ return true;
+ } else {
+ return this.authenticationService.tryLoginWithStoredCredentials()
+ .pipe(
+ map(user => user !== null),
+ tap(isLogged => {
+ if (!isLogged) {
+ this.router.navigate(['welcome']);
+ }
+ })
+ );
+ }
+ }
+}
diff --git a/src/main/angular/src/app/guards/unauthenticated.guard.spec.ts b/src/main/angular/src/app/guards/unauthenticated.guard.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a577f7a0b809e40d5fc402476bf15e3179bbe276
--- /dev/null
+++ b/src/main/angular/src/app/guards/unauthenticated.guard.spec.ts
@@ -0,0 +1,34 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {inject, TestBed} from '@angular/core/testing';
+
+import {UnauthenticatedGuard} from './unauthenticated.guard';
+
+describe('UnauthenticatedGuard', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [UnauthenticatedGuard]
+ });
+ });
+
+ it('should ...', inject([UnauthenticatedGuard], (guard: UnauthenticatedGuard) => {
+ expect(guard).toBeTruthy();
+ }));
+});
diff --git a/src/main/angular/src/app/guards/unauthenticated.guard.ts b/src/main/angular/src/app/guards/unauthenticated.guard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9830980a8b82452b588ef02777f78122305a955e
--- /dev/null
+++ b/src/main/angular/src/app/guards/unauthenticated.guard.ts
@@ -0,0 +1,54 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
+import {map, tap} from 'rxjs/operators';
+import {Observable} from 'rxjs';
+import {AuthenticationService} from '../services/authentication.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UnauthenticatedGuard implements CanActivate {
+ public constructor(
+ private readonly authenticationService: AuthenticationService,
+ private readonly router: Router
+ ) {}
+
+ public canActivate(
+ next: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot
+ ): boolean | Observable {
+ if (this.authenticationService.isLogged()) {
+ this.router.navigate([]);
+ return false;
+ } else {
+ return this.authenticationService.tryLoginWithStoredCredentials()
+ .pipe(
+ map(user => user === null),
+ tap(isNotLogged => {
+ if (!isNotLogged) {
+ this.router.navigate([]);
+ }
+ })
+ );
+ }
+ }
+}
diff --git a/src/main/angular/src/app/interceptors/authentication.interceptor.ts b/src/main/angular/src/app/interceptors/authentication.interceptor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..935b5067dcfbcb0c4c32cee2379e1c280253ebc6
--- /dev/null
+++ b/src/main/angular/src/app/interceptors/authentication.interceptor.ts
@@ -0,0 +1,46 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Injectable} from '@angular/core';
+import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
+import {Observable} from 'rxjs';
+import {AuthenticationService} from '../services/authentication.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthenticationInterceptor implements HttpInterceptor {
+
+ public constructor(public authenticationService: AuthenticationService) {
+ }
+
+ public intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ const user = this.authenticationService.loggedUser;
+
+ if (user !== null) {
+ request = request.clone({
+ setHeaders: {
+ Authorization: 'Basic ' + btoa(user.login + ':' + user.password)
+ }
+ });
+ }
+
+ return next.handle(request);
+ }
+}
diff --git a/src/main/angular/src/app/models/index.ts b/src/main/angular/src/app/models/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..27c91bdb5b87747e7db66e324c5c7dea1f0690cd
--- /dev/null
+++ b/src/main/angular/src/app/models/index.ts
@@ -0,0 +1,20 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export * from './user.model';
diff --git a/src/main/angular/src/app/models/user.model.ts b/src/main/angular/src/app/models/user.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6f2622ab156835a00e2425d40338fbd14bc8adb
--- /dev/null
+++ b/src/main/angular/src/app/models/user.model.ts
@@ -0,0 +1,23 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export interface UserModel {
+ readonly login: string;
+ readonly password: string;
+}
diff --git a/src/main/angular/src/app/modules/people/components/people-form/people-form.component.html b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..af0049eb35da72918caa48d2ea2938bc7de1e873
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.html
@@ -0,0 +1,40 @@
+
+
+
diff --git a/src/main/angular/src/app/modules/people/components/people-form/people-form.component.scss b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..02295a213c980c5a6fbea5120afdd89413712dea
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.scss
@@ -0,0 +1,19 @@
+/*!
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
diff --git a/src/main/angular/src/app/modules/people/components/people-form/people-form.component.spec.ts b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4f8f2c6ec78a199e78ac05c6ab8ed0a6cb5eeaf
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PeopleFormComponent } from './people-form.component';
+
+describe('PeopleFormComponent', () => {
+ let component: PeopleFormComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PeopleFormComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PeopleFormComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/people/components/people-form/people-form.component.ts b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d653b5dd81127bb3d00a6caca90aff4f0dfd181a
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-form/people-form.component.ts
@@ -0,0 +1,71 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {PersonModel} from '../../models/person.model';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-people-form',
+ templateUrl: './people-form.component.html',
+ styleUrls: ['./people-form.component.scss']
+})
+export class PeopleFormComponent {
+ public activePerson: PersonModel;
+
+ @Output()
+ public readonly modify: EventEmitter;
+ @Output()
+ public readonly clean: EventEmitter;
+
+ public name: string;
+ public surname: string;
+
+ public constructor(private readonly router: Router) {
+ this.modify = new EventEmitter();
+ this.clean = new EventEmitter();
+ }
+
+ @Input()
+ public set person(person: PersonModel) {
+ this.activePerson = person;
+ this.name = person.name;
+ this.surname = person.surname;
+ }
+
+ public get person(): PersonModel {
+ return this.activePerson;
+ }
+
+ public onModify() {
+ this.modify.emit({
+ id: this.person.id,
+ name: this.name,
+ surname: this.surname
+ });
+ }
+
+ public onClean() {
+ this.clean.emit();
+ }
+
+ public onPets(){
+ this.router.navigate(['/pets']);
+ }
+}
diff --git a/src/main/angular/src/app/modules/people/components/people-list/people-list.component.html b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..5d7d5e940f5fe9adfddd5c21b64806986662e01d
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
Nombre
+
Apellido
+
+
+
+
+
+
{{person.name}}
+
{{person.surname}}
+
+
+
+
+
+
+
+
diff --git a/src/main/angular/src/app/modules/people/components/people-list/people-list.component.scss b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..02295a213c980c5a6fbea5120afdd89413712dea
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.scss
@@ -0,0 +1,19 @@
+/*!
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
diff --git a/src/main/angular/src/app/modules/people/components/people-list/people-list.component.spec.ts b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9b126f89788e23030877daaec6c47a3924ff7080
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PeopleListComponent } from './people-list.component';
+
+describe('PeopleListComponent', () => {
+ let component: PeopleListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PeopleListComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PeopleListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/people/components/people-list/people-list.component.ts b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..afca24f7cd6522334e3afaea589c3b9be1e063e4
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-list/people-list.component.ts
@@ -0,0 +1,57 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {PersonModel} from '../../models/person.model';
+import {PeopleService} from '../../services/people.service';
+import { Router } from '@angular/router';
+
+
+@Component({
+ selector: 'app-people-list',
+ templateUrl: './people-list.component.html',
+ styleUrls: ['./people-list.component.scss']
+})
+export class PeopleListComponent {
+
+ @Input()
+ public people: PersonModel[] = [];
+
+ @Output()
+ public readonly edit: EventEmitter;
+ @Output()
+ public readonly delete: EventEmitter;
+
+ public constructor(private readonly router : Router) {
+ this.edit = new EventEmitter();
+ this.delete = new EventEmitter();
+ }
+
+ public onEdit(person: PersonModel) {
+ this.edit.emit(person);
+ }
+
+ public onDelete(person: PersonModel) {
+ this.delete.emit(person);
+ }
+
+ public onPets(person: PersonModel) {
+ this.router.navigate(['/pets']);
+ }
+}
diff --git a/src/main/angular/src/app/modules/people/components/people-main/people-main.component.html b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..3d91a434093cb072cfd105c00e99edd2e6cb7f11
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.html
@@ -0,0 +1,22 @@
+
+
+
Personas
+
+
diff --git a/src/main/angular/src/app/modules/people/components/people-main/people-main.component.scss b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..02295a213c980c5a6fbea5120afdd89413712dea
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.scss
@@ -0,0 +1,19 @@
+/*!
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
diff --git a/src/main/angular/src/app/modules/people/components/people-main/people-main.component.spec.ts b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1c7e4062601792d03594caca3fa1a10f22dc6e3d
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PeopleMainComponent } from './people-main.component';
+
+describe('PeopleMainComponent', () => {
+ let component: PeopleMainComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PeopleMainComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PeopleMainComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/people/components/people-main/people-main.component.ts b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c355f188f0be135ae00b962b82f705254d6dfe8f
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/components/people-main/people-main.component.ts
@@ -0,0 +1,89 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component, OnInit } from '@angular/core';
+import {PersonModel} from '../../models/person.model';
+import {PeopleService} from '../../services/people.service';
+import {map, mergeMap} from 'rxjs/operators';
+
+@Component({
+ selector: 'app-people-main',
+ templateUrl: './people-main.component.html',
+ styleUrls: ['./people-main.component.scss']
+})
+export class PeopleMainComponent implements OnInit {
+ public activePerson: PersonModel;
+ public people: PersonModel[];
+
+ public constructor(
+ private readonly peopleService: PeopleService
+ ) {
+ this.people = [];
+ this.clearActivePerson();
+ }
+
+ public ngOnInit(): void {
+ this.peopleService.list()
+ .subscribe(people => this.people = people);
+ }
+
+ public onEdit(person: PersonModel): void {
+ this.activePerson = person;
+ }
+
+ public onDelete(person: PersonModel): void {
+ if (confirm(`¿Estás seguro de que deseas eliminar a ${person.name} ${person.surname}?`)) {
+ this.peopleService.delete(person)
+ .pipe(
+ mergeMap(() => this.peopleService.list())
+ )
+ .subscribe(people => this.people = people);
+ }
+ }
+
+ public onCleanForm(): void {
+ this.clearActivePerson();
+ }
+
+ public onModifyForm(person: PersonModel): void {
+ if (person.id === undefined) {
+ this.peopleService.create(person)
+ .pipe(
+ mergeMap(() => this.peopleService.list())
+ )
+ .subscribe(people => {
+ this.people = people;
+ this.clearActivePerson();
+ });
+ } else {
+ this.peopleService.modify(person)
+ .pipe(
+ mergeMap(() => this.peopleService.list())
+ )
+ .subscribe(people => {
+ this.people = people;
+ this.clearActivePerson();
+ });
+ }
+ }
+
+ private clearActivePerson(): void {
+ this.activePerson = { id: undefined, name: '', surname: '' };
+ }
+}
diff --git a/src/main/angular/src/app/modules/people/models/person.model.ts b/src/main/angular/src/app/modules/people/models/person.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b63252de6c0f87fda7c6dec40d72bdc98241be9d
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/models/person.model.ts
@@ -0,0 +1,24 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export class PersonModel {
+ id?: number;
+ name: string;
+ surname: string;
+}
diff --git a/src/main/angular/src/app/modules/people/people-routing.module.ts b/src/main/angular/src/app/modules/people/people-routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..196264783618b216dd0f6750472213f7bf33c6a4
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/people-routing.module.ts
@@ -0,0 +1,35 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {PeopleMainComponent} from './components/people-main/people-main.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: PeopleMainComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class PeopleRoutingModule { }
diff --git a/src/main/angular/src/app/modules/people/people.module.ts b/src/main/angular/src/app/modules/people/people.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0265f4b90a910343ef1ada1a9a1d25494d882a5a
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/people.module.ts
@@ -0,0 +1,42 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+import {PeopleRoutingModule} from './people-routing.module';
+import {PeopleListComponent} from './components/people-list/people-list.component';
+import {PeopleFormComponent} from './components/people-form/people-form.component';
+import {PeopleMainComponent} from './components/people-main/people-main.component';
+import {FormsModule} from '@angular/forms';
+
+@NgModule({
+ declarations: [
+ PeopleFormComponent,
+ PeopleListComponent,
+ PeopleMainComponent
+ ],
+ imports: [
+ CommonModule,
+ FormsModule,
+ PeopleRoutingModule
+ ]
+})
+export class PeopleModule {
+}
diff --git a/src/main/angular/src/app/modules/people/services/people.service.spec.ts b/src/main/angular/src/app/modules/people/services/people.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56f3dfaa1a014eb6abdbb2c4f1ebd6e0d904a8f4
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/services/people.service.spec.ts
@@ -0,0 +1,31 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { PeopleService } from './people.service';
+
+describe('PeopleService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: PeopleService = TestBed.get(PeopleService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/people/services/people.service.ts b/src/main/angular/src/app/modules/people/services/people.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1011e9b0c35f825bd9131642f0b8e3c893a85294
--- /dev/null
+++ b/src/main/angular/src/app/modules/people/services/people.service.ts
@@ -0,0 +1,56 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
+import {environment} from '../../../../environments/environment';
+import {Observable} from 'rxjs';
+import {PersonModel} from '../models/person.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PeopleService {
+
+ public constructor(private readonly http: HttpClient) { }
+
+ public list(): Observable {
+ return this.http.get(`${environment.restApi}/people`);
+ }
+
+ public create(person: PersonModel): Observable {
+ const data = new HttpParams()
+ .set('name', person.name)
+ .set('surname', person.surname);
+
+ return this.http.post(`${environment.restApi}/people`, data);
+ }
+
+ public modify(person: PersonModel): Observable {
+ const data = new HttpParams()
+ .set('name', person.name)
+ .set('surname', person.surname);
+
+ return this.http.put(`${environment.restApi}/people/${person.id}`, data);
+ }
+
+ public delete(person: PersonModel): Observable {
+ return this.http.delete(`${environment.restApi}/people/${person.id}`);
+ }
+}
diff --git a/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.html b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..0e8a20314b676c02d565d2e84aee3927a3945a03
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.html
@@ -0,0 +1,47 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.scss b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.spec.ts b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..80b704deb42832f5c6a0025222014aa223f7f4a8
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PetsFormComponent } from '../pets-form/pets-form.component';
+
+describe('PetsFormComponent', () => {
+ let component: PetsFormComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PetsFormComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PetsFormComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.ts b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a4e318980fd2f29bbf48cd220449dc35ee1a8290
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-form/pets-form.component.ts
@@ -0,0 +1,75 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {PetModel} from '../../models/pet.model';
+import { PersonModel } from 'src/app/modules/people/models/person.model';
+import { PeopleService } from 'src/app/modules/people/services/people.service';
+
+@Component({
+ selector: 'app-pets-form',
+ templateUrl: './pets-form.component.html',
+ styleUrls: ['./pets-form.component.scss']
+})
+export class PetsFormComponent {
+ public activePet: PetModel;
+ public people: PersonModel[] = [];
+
+ @Output()
+ public readonly modify: EventEmitter;
+ @Output()
+ public readonly clean: EventEmitter;
+
+ public name: string;
+ public owner: number;
+ public typeOfAnimal: string;
+
+ public constructor(private readonly peopleService: PeopleService) {
+ this.modify = new EventEmitter();
+ this.clean = new EventEmitter();
+ this.peopleService.list()
+ .subscribe(people => this.people = people);
+ }
+
+ @Input()
+ public set pet(pet: PetModel) {
+ this.activePet = pet;
+ this.name = pet.name;
+ this.owner = pet.owner;
+ this.typeOfAnimal = pet.typeOfAnimal;
+ }
+
+ public get pet(): PetModel {
+ return this.activePet;
+ }
+
+ public onModify() {
+ console.log(this.typeOfAnimal);
+ this.modify.emit({
+ id: this.pet.id,
+ name: this.name,
+ owner: this.owner,
+ typeOfAnimal: this.typeOfAnimal
+ });
+ }
+
+ public onClean() {
+ this.clean.emit();
+ }
+}
diff --git a/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.html b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..608b15660fa78f4c0f9d21ba11933e5b120b2916
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
Nombre
+
Dueño
+
Tipo de Animal
+
+
+
+
+
{{pet.name}}
+
{{owner(pet.owner)}}
+
{{pet.typeOfAnimal}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.scss b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.spec.ts b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..87d9e56f7924b94c89da4973f23e539cd253007d
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PetsListComponent } from './pets-list.component';
+
+describe('PetsListComponent', () => {
+ let component: PetsListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PetsListComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PetsListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.ts b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73147358e9192cf384c142e94db63e74416329ba
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-list/pets-list.component.ts
@@ -0,0 +1,77 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {PetModel} from '../../models/pet.model';
+import {PetsService} from '../../services/pets.service';
+import { PersonModel } from 'src/app/modules/people/models/person.model';
+import { PeopleService } from 'src/app/modules/people/services/people.service';
+
+
+@Component({
+ selector: 'app-pets-list',
+ templateUrl: './pets-list.component.html',
+ styleUrls: ['./pets-list.component.scss']
+})
+export class PetsListComponent {
+
+ @Input()
+ public pets: PetModel[] = [];
+ public people: PersonModel[] = [];
+
+
+ @Output()
+ public readonly edit: EventEmitter;
+ @Output()
+ public readonly delete: EventEmitter;
+
+ public constructor(private readonly peopleService: PeopleService
+ ) {
+ this.edit = new EventEmitter();
+ this.delete = new EventEmitter();
+ this.peopleService.list()
+ .subscribe(people => this.people = people);
+ }
+
+
+
+ public onEdit(pets: PetModel) {
+ this.edit.emit(pets);
+ }
+
+ public onDelete(pets: PetModel) {
+ this.delete.emit(pets);
+ }
+
+ public owner(idPersona: number){
+
+ var toret = "" ;
+
+ this.people.forEach(element => {
+ if(element.id==idPersona){
+ toret=element.name+" ";
+ toret+=element.surname;
+
+ }
+ });
+
+ return toret;
+ }
+
+}
diff --git a/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.html b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..2a5938e7f1bc8d3d4b30b3b32c14edd6ad585f06
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.html
@@ -0,0 +1,23 @@
+
+
+
Mascotas
+
+
+
\ No newline at end of file
diff --git a/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.scss b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.spec.ts b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3d96317bb38e458c12487477172d31e424c53b7
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PetsMainComponent } from './pets-main.component';
+
+describe('PetsMainComponent', () => {
+ let component: PetsMainComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PetsMainComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PetsMainComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.ts b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ec97dba3431c9dd4cf47abbcc9489fd7b1c4e366
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/components/pets-main/pets-main.component.ts
@@ -0,0 +1,89 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component, OnInit } from '@angular/core';
+import {PetModel} from '../../models/pet.model';
+import {PetsService} from '../../services/pets.service';
+import {map, mergeMap} from 'rxjs/operators';
+
+@Component({
+ selector: 'app-pets-main',
+ templateUrl: './pets-main.component.html',
+ styleUrls: ['./pets-main.component.scss']
+})
+export class PetsMainComponent implements OnInit {
+ public activePet: PetModel;
+ public pets: PetModel[];
+
+ public constructor(
+ private readonly petsService: PetsService
+ ) {
+ this.pets = [];
+ this.clearActivePet();
+ }
+
+ public ngOnInit(): void {
+ this.petsService.list()
+ .subscribe(pets => this.pets = pets);
+ }
+
+ public onEdit(pet: PetModel): void {
+ this.activePet = pet;
+ }
+
+ public onDelete(pet: PetModel): void {
+ if (confirm(`¿Estás seguro de que deseas eliminar a ${pet.name} ${pet.owner} ${pet.typeOfAnimal}?`)) {
+ this.petsService.delete(pet)
+ .pipe(
+ mergeMap(() => this.petsService.list())
+ )
+ .subscribe(pets => this.pets = pets);
+ }
+ }
+
+ public onCleanForm(): void {
+ this.clearActivePet();
+ }
+
+ public onModifyForm(pet: PetModel): void {
+ if (pet.id === undefined) {
+ this.petsService.create(pet)
+ .pipe(
+ mergeMap(() => this.petsService.list())
+ )
+ .subscribe(pets => {
+ this.pets = pets;
+ this.clearActivePet();
+ });
+ } else {
+ this.petsService.modify(pet)
+ .pipe(
+ mergeMap(() => this.petsService.list())
+ )
+ .subscribe(pets => {
+ this.pets = pets;
+ this.clearActivePet();
+ });
+ }
+ }
+
+ private clearActivePet(): void {
+ this.activePet = { id: undefined, name: '', owner: undefined, typeOfAnimal:'' };
+ }
+}
diff --git a/src/main/angular/src/app/modules/pets/models/pet.model.ts b/src/main/angular/src/app/modules/pets/models/pet.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b54ebfbf56d29aa4ef34942d87b9d837a5537bda
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/models/pet.model.ts
@@ -0,0 +1,26 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export class PetModel {
+ id?: number;
+ name: string;
+ owner: number;
+ typeOfAnimal: string;
+ }
+
\ No newline at end of file
diff --git a/src/main/angular/src/app/modules/pets/pets-routing.module.ts b/src/main/angular/src/app/modules/pets/pets-routing.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5fa4f691b18e532d1858adcfe80627c9742068e
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/pets-routing.module.ts
@@ -0,0 +1,35 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {PetsMainComponent} from './components/pets-main/pets-main.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: PetsMainComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class PetsRoutingModule { }
diff --git a/src/main/angular/src/app/modules/pets/pets.module.ts b/src/main/angular/src/app/modules/pets/pets.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4d0a74bce986fc53e1b4fd9d0f168e1a28e0399c
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/pets.module.ts
@@ -0,0 +1,42 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+import {PetsRoutingModule} from './pets-routing.module';
+import {PetsListComponent} from './components/pets-list/pets-list.component';
+import {PetsFormComponent} from './components/pets-form/pets-form.component';
+import {PetsMainComponent} from './components/pets-main/pets-main.component';
+import {FormsModule} from '@angular/forms';
+
+@NgModule({
+ declarations: [
+ PetsFormComponent,
+ PetsListComponent,
+ PetsMainComponent
+ ],
+ imports: [
+ CommonModule,
+ FormsModule,
+ PetsRoutingModule
+ ]
+})
+export class PetsModule {
+}
diff --git a/src/main/angular/src/app/modules/pets/services/pets.service.spec.ts b/src/main/angular/src/app/modules/pets/services/pets.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a09130decc02274207bcdb3d3fc3a820e86fe613
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/services/pets.service.spec.ts
@@ -0,0 +1,31 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { PetsService } from './pets.service';
+
+describe('PetsService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: PetsService = TestBed.get(PetsService);
+ expect(service).toBeTruthy();
+ });
+});
\ No newline at end of file
diff --git a/src/main/angular/src/app/modules/pets/services/pets.service.ts b/src/main/angular/src/app/modules/pets/services/pets.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..31bec67bd929b57b2e26a9cc42608505413533ba
--- /dev/null
+++ b/src/main/angular/src/app/modules/pets/services/pets.service.ts
@@ -0,0 +1,58 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
+import {environment} from '../../../../environments/environment';
+import {Observable} from 'rxjs';
+import {PetModel} from '../models/pet.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PetsService {
+
+ public constructor(private readonly http: HttpClient) { }
+
+ public list(): Observable {
+ return this.http.get(`${environment.restApi}/pets`);
+ }
+
+ public create(pet: PetModel): Observable {
+ const data = new HttpParams()
+ .set('name', pet.name)
+ .set('owner', pet.owner.toString())
+ .set('typeOfAnimal', pet.typeOfAnimal);
+
+ return this.http.post(`${environment.restApi}/pets`, data);
+ }
+
+ public modify(pet: PetModel): Observable {
+ const data = new HttpParams()
+ .set('name', pet.name)
+ .set('owner', pet.owner.toString())
+ .set('typeOfAnimal', pet.typeOfAnimal);
+
+ return this.http.put(`${environment.restApi}/pets/${pet.id}`, data);
+ }
+
+ public delete(pet: PetModel): Observable {
+ return this.http.delete(`${environment.restApi}/pets/${pet.id}`);
+ }
+}
diff --git a/src/main/angular/src/app/services/authentication.service.spec.ts b/src/main/angular/src/app/services/authentication.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..79f6dc172f2b0d6e6b9563571fa7216f2604fb5c
--- /dev/null
+++ b/src/main/angular/src/app/services/authentication.service.spec.ts
@@ -0,0 +1,31 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { AuthenticationService } from './authentication.service';
+
+describe('AuthenticationService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: AuthenticationService = TestBed.get(AuthenticationService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/main/angular/src/app/services/authentication.service.ts b/src/main/angular/src/app/services/authentication.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db7a4be7d27a941961051777c84197ae035d1d75
--- /dev/null
+++ b/src/main/angular/src/app/services/authentication.service.ts
@@ -0,0 +1,125 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import {Injectable} from '@angular/core';
+import {BehaviorSubject, Observable, of} from 'rxjs';
+import {UserModel} from '../models';
+import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
+import {environment} from '../../environments/environment';
+import {catchError, tap} from 'rxjs/operators';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthenticationService {
+ private static readonly USER_STORAGE_KEY = 'user';
+
+ private readonly _loggedUser$: BehaviorSubject;
+
+ private static clearCredentials(): void {
+ localStorage.removeItem(AuthenticationService.USER_STORAGE_KEY);
+ }
+
+ private static loadCredentials(): UserModel | null {
+ const serializedCredentials = localStorage.getItem(AuthenticationService.USER_STORAGE_KEY);
+
+ if (serializedCredentials !== null) {
+ return JSON.parse(serializedCredentials);
+ } else {
+ return null;
+ }
+ }
+
+ private static storeCredentials(login: string, password: string): void {
+ const credentials: UserModel = {login, password};
+
+ localStorage.setItem(AuthenticationService.USER_STORAGE_KEY, JSON.stringify(credentials));
+ }
+
+ public constructor(
+ private readonly http: HttpClient
+ ) {
+ this._loggedUser$ = new BehaviorSubject(null);
+ }
+
+ public get loggedUser$(): Observable {
+ return this._loggedUser$.asObservable();
+ }
+
+ public get loggedUser(): UserModel | null {
+ return this._loggedUser$.value;
+ }
+
+ public isLogged(): boolean {
+ return this.loggedUser !== null;
+ }
+
+ public logout(): void {
+ AuthenticationService.clearCredentials();
+ this._loggedUser$.next(null);
+ }
+
+ public tryLoginWithStoredCredentials(): Observable {
+ const credentials = AuthenticationService.loadCredentials();
+
+ if (credentials !== null) {
+ return this.tryLogin(credentials);
+ } else {
+ return of(null);
+ }
+ }
+
+ public tryLogin(login: UserModel): Observable;
+ public tryLogin(login: string, password: string): Observable;
+
+ public tryLogin(login: string | UserModel, password?: string): Observable {
+ let userLogin: string;
+ let userPassword: string;
+
+ if (typeof login === 'string') {
+ if (password === undefined) {
+ throw new TypeError('password can\'t be null when login is string');
+ }
+
+ userLogin = login;
+ userPassword = password;
+ } else {
+ userLogin = login.login;
+ userPassword = login.password;
+ }
+ AuthenticationService.storeCredentials(userLogin, userPassword);
+
+ const headers = new HttpHeaders()
+ .set('Authorization', 'Basic ' + btoa(userLogin + ':' + userPassword));
+
+ return this.http.get(`${environment.restApi}/users/${userLogin}`, {
+ headers: headers
+ })
+ .pipe(
+ tap(user => this._loggedUser$.next({
+ login: userLogin,
+ password: userPassword
+ })),
+ catchError(error => {
+ this._loggedUser$.next(null);
+ throw error;
+ })
+ );
+ }
+}
diff --git a/src/main/angular/src/assets/.gitkeep b/src/main/angular/src/assets/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/main/angular/src/environments/environment.prod.ts b/src/main/angular/src/environments/environment.prod.ts
new file mode 100644
index 0000000000000000000000000000000000000000..09519c5d922d9d42a5313b2850f1651ab328f3c6
--- /dev/null
+++ b/src/main/angular/src/environments/environment.prod.ts
@@ -0,0 +1,23 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export const environment = {
+ production: true,
+ restApi: 'http://daaexample.com:9080/DAAExample/rest'
+};
diff --git a/src/main/angular/src/environments/environment.ts b/src/main/angular/src/environments/environment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3e33aa3b479f006d3b2522177eea4caf60e1fc79
--- /dev/null
+++ b/src/main/angular/src/environments/environment.ts
@@ -0,0 +1,36 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false,
+ restApi: 'http://localhost:9080/DAAExample/rest'
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/src/main/angular/src/favicon.ico b/src/main/angular/src/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..8081c7ceaf2be08bf59010158c586170d9d2d517
Binary files /dev/null and b/src/main/angular/src/favicon.ico differ
diff --git a/src/main/angular/src/index.html b/src/main/angular/src/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..aa0636053593fccfe0de5f38d1032c3289f0c90b
--- /dev/null
+++ b/src/main/angular/src/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ DAA Example
+
+
+
+
+
+
+
+
+
diff --git a/src/main/angular/src/karma.conf.js b/src/main/angular/src/karma.conf.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddbda7270d386e1a6905395309e184500cdf8e3d
--- /dev/null
+++ b/src/main/angular/src/karma.conf.js
@@ -0,0 +1,50 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
diff --git a/src/main/angular/src/main.ts b/src/main/angular/src/main.ts
new file mode 100644
index 0000000000000000000000000000000000000000..160d71f8d873311426e50035bd3c29154f36dc01
--- /dev/null
+++ b/src/main/angular/src/main.ts
@@ -0,0 +1,31 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+ .catch(err => console.error(err));
diff --git a/src/main/angular/src/polyfills.ts b/src/main/angular/src/polyfills.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ea6a00c4c31e00486d565d84b26bf1284cf2ac41
--- /dev/null
+++ b/src/main/angular/src/polyfills.ts
@@ -0,0 +1,99 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/**
+ * If the application will be indexed by Google Search, the following is required.
+ * Googlebot uses a renderer based on Chrome 41.
+ * https://developers.google.com/search/docs/guides/rendering
+ **/
+// import 'core-js/es6/array';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ **/
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ */
+
+ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+
+ /*
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ */
+// (window as any).__Zone_enable_cross_context_check = true;
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/src/main/angular/src/styles.scss b/src/main/angular/src/styles.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a78f5ad76b92440cb4f1d0f84a8b5d5be3d3778b
--- /dev/null
+++ b/src/main/angular/src/styles.scss
@@ -0,0 +1,24 @@
+/*!
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/* You can add global styles to this file, and also import other style files */
+
+html, body {
+ height: 100%;
+}
diff --git a/src/main/angular/src/test.ts b/src/main/angular/src/test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..80dab776175a62be856061169aa0c88baa7f12ee
--- /dev/null
+++ b/src/main/angular/src/test.ts
@@ -0,0 +1,39 @@
+/*
+ * DAA Example
+ *
+ * Copyright (C) 2019 - Miguel Reboiro-Jato.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/src/main/angular/src/tsconfig.app.json b/src/main/angular/src/tsconfig.app.json
new file mode 100644
index 0000000000000000000000000000000000000000..190fd300b601e168a3d185dbfa2758ab73257479
--- /dev/null
+++ b/src/main/angular/src/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/src/main/angular/src/tsconfig.spec.json b/src/main/angular/src/tsconfig.spec.json
new file mode 100644
index 0000000000000000000000000000000000000000..de7733630eb224b246854a20934f792051926e8f
--- /dev/null
+++ b/src/main/angular/src/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/src/main/angular/src/tslint.json b/src/main/angular/src/tslint.json
new file mode 100644
index 0000000000000000000000000000000000000000..52e2c1a5a74ce268bec92d34cd3bca751624adb3
--- /dev/null
+++ b/src/main/angular/src/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/src/main/angular/tsconfig.json b/src/main/angular/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..26e538b10d5650d8f98a4a040c29c65f215fef89
--- /dev/null
+++ b/src/main/angular/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "downlevelIteration": true,
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "importHelpers": true,
+ "target": "es2015",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2018",
+ "dom"
+ ]
+ }
+}
diff --git a/src/main/angular/tslint.json b/src/main/angular/tslint.json
new file mode 100644
index 0000000000000000000000000000000000000000..ea31655252e508391e2514dd63e9c9c6e7113dd5
--- /dev/null
+++ b/src/main/angular/tslint.json
@@ -0,0 +1,131 @@
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "no-output-on-prefix": true,
+ "no-inputs-metadata-property": true,
+ "no-outputs-metadata-property": true,
+ "no-host-metadata-property": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-lifecycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/DAAExampleApplication.java b/src/main/java/es/uvigo/esei/daa/DAAExampleApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f3a5c0439740d2d340628ab5063fc3b7224fce2
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/DAAExampleApplication.java
@@ -0,0 +1,42 @@
+package es.uvigo.esei.daa;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+import es.uvigo.esei.daa.rest.PeopleResource;
+import es.uvigo.esei.daa.rest.UsersResource;
+import es.uvigo.esei.daa.rest.PetsResource;
+
+/**
+ * Configuration of the REST application. This class includes the resources and
+ * configuration parameter used in the REST API of the application.
+ *
+ * @author Miguel Reboiro Jato
+ *
+ */
+@ApplicationPath("/rest/*")
+public class DAAExampleApplication extends Application {
+ @Override
+ public Set> getClasses() {
+ return Stream.of(
+ PeopleResource.class,
+ UsersResource.class,
+ PetsResource.class
+ ).collect(toSet());
+ }
+
+ @Override
+ public Map getProperties() {
+ // Activates JSON automatic conversion in JAX-RS
+ return Collections.singletonMap(
+ "com.sun.jersey.api.json.POJOMappingFeature", true
+ );
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/dao/DAO.java b/src/main/java/es/uvigo/esei/daa/dao/DAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..18568740a647888a33ac61160285b631bdc01366
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/dao/DAO.java
@@ -0,0 +1,47 @@
+package es.uvigo.esei.daa.dao;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+/**
+ * Simple base class for DAO (Data Access Object) classes. This super-class is
+ * responsible for providing a {@link java.sql.Connection} to its sub-classes.
+ *
+ * @author Miguel Reboiro Jato
+ *
+ */
+public abstract class DAO {
+ private final static Logger LOG = Logger.getLogger(DAO.class.getName());
+ private final static String JNDI_NAME = "java:/comp/env/jdbc/daaexample";
+
+ private DataSource dataSource;
+
+ /**
+ * Constructs a new instance of {@link DAO}.
+ */
+ public DAO() {
+ try {
+ this.dataSource = (DataSource) new InitialContext().lookup(JNDI_NAME);
+ } catch (NamingException e) {
+ LOG.log(Level.SEVERE, "Error initializing DAO", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns an open {@link java.sql.Connection}.
+ *
+ * @return an open {@link java.sql.Connection}.
+ * @throws SQLException if an error happens while establishing the
+ * connection with the database.
+ */
+ protected Connection getConnection() throws SQLException {
+ return this.dataSource.getConnection();
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/dao/DAOException.java b/src/main/java/es/uvigo/esei/daa/dao/DAOException.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffd4233f07cfcbbed73c264c235efd0317b9ffcf
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/dao/DAOException.java
@@ -0,0 +1,83 @@
+package es.uvigo.esei.daa.dao;
+
+/**
+ * A general exception class for the DAO layer.
+ *
+ * @author Miguel Reboiro Jato
+ */
+public class DAOException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new instance of {@link DAOException} with {@code null} as
+ * its detail message.
+ * The cause is not initialized, and may subsequently be initialized by a
+ * call to {@link #initCause}.
+ *
+ */
+ public DAOException() {
+ }
+
+ /**
+ * Constructs a new instance of {@link DAOException} with the specified
+ * detail message. The cause is not initialized, and may subsequently be
+ * initialized by a call to {@link #initCause}.
+ *
+ * @param message the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ */
+ public DAOException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new instance of {@link DAOException} with the specified
+ * cause and a detail message of
+ * {@code (cause==null ? null : cause.toString())} (which typically contains
+ * the class and detail message of {@code cause}). This constructor is
+ * useful for exceptions that are little more than wrappers for other
+ * throwables (for example, {@link java.security.PrivilegedActionException}).
+ *
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A {@code null} value is permitted, and
+ * indicates that the cause is nonexistent or unknown.)
+ */
+ public DAOException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a new instance of {@link DAOException} with the specified
+ * detail message and cause.
+ *
+ *
Note that the detail message associated with {@code cause} is
+ * not automatically incorporated in this exception's detail message.
+ *
+ * @param message the detail message (which is saved for later retrieval
+ * by the {@link #getMessage()} method).
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). A {@code null} value is permitted, and
+ * indicates that the cause is nonexistent or unknown.
+ */
+ public DAOException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new instance of {@link DAOException} with the specified
+ * detail message, cause, suppression enabled or disabled, and writable
+ * stack trace enabled or disabled.
+ *
+ * @param message the detail message.
+ * @param cause the cause. A {@code null} value is permitted, and indicates
+ * that the cause is nonexistent or unknown.
+ * @param enableSuppression whether or not suppression is enabled or
+ * disabled.
+ * @param writableStackTrace whether or not the stack trace should be
+ * writable.
+ */
+ public DAOException(String message, Throwable cause,
+ boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/dao/PeopleDAO.java b/src/main/java/es/uvigo/esei/daa/dao/PeopleDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d99edb26140ee773f06c8afbf0f40923f1848b2
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/dao/PeopleDAO.java
@@ -0,0 +1,191 @@
+package es.uvigo.esei.daa.dao;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import es.uvigo.esei.daa.entities.Person;
+
+/**
+ * DAO class for the {@link Person} entities.
+ *
+ * @author Miguel Reboiro Jato
+ *
+ */
+public class PeopleDAO extends DAO {
+ private final static Logger LOG = Logger.getLogger(PeopleDAO.class.getName());
+
+ /**
+ * Returns a person stored persisted in the system.
+ *
+ * @param id identifier of the person.
+ * @return a person with the provided identifier.
+ * @throws DAOException if an error happens while retrieving the person.
+ * @throws IllegalArgumentException if the provided id does not corresponds
+ * with any persisted person.
+ */
+ public Person get(int id)
+ throws DAOException, IllegalArgumentException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "SELECT * FROM people WHERE id=?";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setInt(1, id);
+
+ try (final ResultSet result = statement.executeQuery()) {
+ if (result.next()) {
+ return rowToEntity(result);
+ } else {
+ throw new IllegalArgumentException("Invalid id");
+ }
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error getting a person", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Returns a list with all the people persisted in the system.
+ *
+ * @return a list with all the people persisted in the system.
+ * @throws DAOException if an error happens while retrieving the people.
+ */
+ public List list() throws DAOException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "SELECT * FROM people";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ try (final ResultSet result = statement.executeQuery()) {
+ final List people = new LinkedList<>();
+
+ while (result.next()) {
+ people.add(rowToEntity(result));
+ }
+
+ return people;
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error listing people", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Persists a new person in the system. An identifier will be assigned
+ * automatically to the new person.
+ *
+ * @param name name of the new person. Can't be {@code null}.
+ * @param surname surname of the new person. Can't be {@code null}.
+ * @return a {@link Person} entity representing the persisted person.
+ * @throws DAOException if an error happens while persisting the new person.
+ * @throws IllegalArgumentException if the name or surname are {@code null}.
+ */
+ public Person add(String name, String surname)
+ throws DAOException, IllegalArgumentException {
+ if (name == null || surname == null) {
+ throw new IllegalArgumentException("name and surname can't be null");
+ }
+
+ try (Connection conn = this.getConnection()) {
+ final String query = "INSERT INTO people VALUES(null, ?, ?)";
+
+ try (PreparedStatement statement = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
+ statement.setString(1, name);
+ statement.setString(2, surname);
+
+ if (statement.executeUpdate() == 1) {
+ try (ResultSet resultKeys = statement.getGeneratedKeys()) {
+ if (resultKeys.next()) {
+ return new Person(resultKeys.getInt(1), name, surname);
+ } else {
+ LOG.log(Level.SEVERE, "Error retrieving inserted id");
+ throw new SQLException("Error retrieving inserted id");
+ }
+ }
+ } else {
+ LOG.log(Level.SEVERE, "Error inserting value");
+ throw new SQLException("Error inserting value");
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error adding a person", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Modifies a person previously persisted in the system. The person will be
+ * retrieved by the provided id and its current name and surname will be
+ * replaced with the provided.
+ *
+ * @param person a {@link Person} entity with the new data.
+ * @throws DAOException if an error happens while modifying the new person.
+ * @throws IllegalArgumentException if the person is {@code null}.
+ */
+ public void modify(Person person)
+ throws DAOException, IllegalArgumentException {
+ if (person == null) {
+ throw new IllegalArgumentException("person can't be null");
+ }
+
+ try (Connection conn = this.getConnection()) {
+ final String query = "UPDATE people SET name=?, surname=? WHERE id=?";
+
+ try (PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setString(1, person.getName());
+ statement.setString(2, person.getSurname());
+ statement.setInt(3, person.getId());
+
+ if (statement.executeUpdate() != 1) {
+ throw new IllegalArgumentException("name and surname can't be null");
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error modifying a person", e);
+ throw new DAOException();
+ }
+ }
+
+ /**
+ * Removes a persisted person from the system.
+ *
+ * @param id identifier of the person to be deleted.
+ * @throws DAOException if an error happens while deleting the person.
+ * @throws IllegalArgumentException if the provided id does not corresponds
+ * with any persisted person.
+ */
+ public void delete(int id)
+ throws DAOException, IllegalArgumentException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "DELETE FROM people WHERE id=?";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setInt(1, id);
+
+ if (statement.executeUpdate() != 1) {
+ throw new IllegalArgumentException("Invalid id");
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error deleting a person", e);
+ throw new DAOException(e);
+ }
+ }
+
+ private Person rowToEntity(ResultSet row) throws SQLException {
+ return new Person(
+ row.getInt("id"),
+ row.getString("name"),
+ row.getString("surname")
+ );
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/dao/PetsDAO.java b/src/main/java/es/uvigo/esei/daa/dao/PetsDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..77de998df0afeafdf4e29c081f1b6a510b7d4501
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/dao/PetsDAO.java
@@ -0,0 +1,194 @@
+package es.uvigo.esei.daa.dao;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import es.uvigo.esei.daa.entities.Pet;
+
+/**
+ * DAO class for the {@link Pet} entities.
+ *
+ * @author Miguel Reboiro Jato
+ *
+ */
+public class PetsDAO extends DAO {
+ private final static Logger LOG = Logger.getLogger(PetsDAO.class.getName());
+
+ /**
+ * Returns a pet stored persisted in the system.
+ *
+ * @param id identifier of the pet.
+ * @return a pet 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 Pet get(int id)
+ throws DAOException, IllegalArgumentException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "SELECT * FROM pets WHERE id=?";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setInt(1, id);
+
+ try (final ResultSet result = statement.executeQuery()) {
+ if (result.next()) {
+ return rowToEntity(result);
+ } else {
+ throw new IllegalArgumentException("Invalid id");
+ }
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error getting a pet", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Returns a list with all the people persisted in the system.
+ *
+ * @return a list with all the people persisted in the system.
+ * @throws DAOException if an error happens while retrieving the people.
+ */
+ public List list() throws DAOException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "SELECT * FROM pets";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ try (final ResultSet result = statement.executeQuery()) {
+ final List pets = new LinkedList<>();
+
+ while (result.next()) {
+ pets.add(rowToEntity(result));
+ }
+
+ return pets;
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error listing pets", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Persists a new person in the system. An identifier will be assigned
+ * automatically to the new person.
+ *
+ * @param name name of the new person. Can't be {@code null}.
+ * @param surname surname of the new person. Can't be {@code null}.
+ * @return a {@link Person} entity representing the persisted person.
+ * @throws DAOException if an error happens while persisting the new person.
+ * @throws IllegalArgumentException if the name or surname are {@code null}.
+ */
+ public Pet add(String name, Integer owner, String typeOfAnimal)
+ throws DAOException, IllegalArgumentException {
+ if (name == null || owner == null || typeOfAnimal==null ) {
+ throw new IllegalArgumentException("name, owner and type of animal can't be null");
+ }
+
+ try (Connection conn = this.getConnection()) {
+ final String query = "INSERT INTO pets VALUES(null, ?, ?, ?)";
+
+ try (PreparedStatement statement = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
+ statement.setString(1, name);
+ statement.setInt(2, owner);
+ statement.setString(3, typeOfAnimal);
+
+ if (statement.executeUpdate() == 1) {
+ try (ResultSet resultKeys = statement.getGeneratedKeys()) {
+ if (resultKeys.next()) {
+ return new Pet(resultKeys.getInt(1), name, owner, typeOfAnimal);
+ } else {
+ LOG.log(Level.SEVERE, "Error retrieving inserted id");
+ throw new SQLException("Error retrieving inserted id");
+ }
+ }
+ } else {
+ LOG.log(Level.SEVERE, "Error inserting value");
+ throw new SQLException("Error inserting value");
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error adding a person", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Modifies a person previously persisted in the system. The person will be
+ * retrieved by the provided id and its current name and surname will be
+ * replaced with the provided.
+ *
+ * @param pet a {@link Pet} 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(Pet pet)
+ throws DAOException, IllegalArgumentException {
+ if (pet == null) {
+ throw new IllegalArgumentException("Pet can't be null");
+ }
+
+ try (Connection conn = this.getConnection()) {
+ final String query = "UPDATE pets SET name=?, owner=?, typeOfAnimal=? WHERE id=?";
+
+ try (PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setString(1, pet.getName());
+ statement.setInt(2, pet.getOwner());
+ statement.setString(3, pet.getTypeOfAnimal());
+ statement.setInt(4, pet.getId());
+
+ if (statement.executeUpdate() != 1) {
+ throw new IllegalArgumentException("name, owner and type of animal can't be null");
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error modifying a pet", e);
+ throw new DAOException();
+ }
+ }
+
+ /**
+ * Removes a persisted person from the system.
+ *
+ * @param id identifier of the person to be deleted.
+ * @throws DAOException if an error happens while deleting the person.
+ * @throws IllegalArgumentException if the provided id does not corresponds
+ * with any persisted person.
+ */
+ public void delete(int id)
+ throws DAOException, IllegalArgumentException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "DELETE FROM pets WHERE id=?";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setInt(1, id);
+
+ if (statement.executeUpdate() != 1) {
+ throw new IllegalArgumentException("Invalid id");
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error deleting a pets", e);
+ throw new DAOException(e);
+ }
+ }
+
+ private Pet rowToEntity(ResultSet row) throws SQLException {
+ return new Pet(
+ row.getInt("id"),
+ row.getString("name"),
+ row.getInt("owner"),
+ row.getString("typeOfAnimal")
+ );
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java b/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..70d47b233f0c7fdfff0fd48fca10a463eed124f0
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java
@@ -0,0 +1,107 @@
+package es.uvigo.esei.daa.dao;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import es.uvigo.esei.daa.entities.User;
+
+/**
+ * DAO class for managing the users of the system.
+ *
+ * @author Miguel Reboiro Jato
+ */
+public class UsersDAO extends DAO {
+ private final static Logger LOG = Logger.getLogger(UsersDAO.class.getName());
+
+ /**
+ * Returns a user stored persisted in the system.
+ *
+ * @param login the login of the user to be retrieved.
+ * @return a user with the provided login.
+ * @throws DAOException if an error happens while retrieving the user.
+ * @throws IllegalArgumentException if the provided login does not
+ * corresponds with any persisted user.
+ */
+ public User get(String login) throws DAOException {
+ try (final Connection conn = this.getConnection()) {
+ final String query = "SELECT * FROM users WHERE login=?";
+
+ try (final PreparedStatement statement = conn.prepareStatement(query)) {
+ statement.setString(1, login);
+
+ try (final ResultSet result = statement.executeQuery()) {
+ if (result.next()) {
+ return rowToEntity(result);
+ } else {
+ throw new IllegalArgumentException("Invalid id");
+ }
+ }
+ }
+ } catch (SQLException e) {
+ LOG.log(Level.SEVERE, "Error checking login", e);
+ throw new DAOException(e);
+ }
+ }
+
+ /**
+ * Checks if the provided credentials (login and password) correspond with a
+ * valid user registered in the system.
+ *
+ *
The password is stored in the system "salted" and encoded with the
+ * SHA-256 algorithm.
+ *
+ * @param login the login of the user.
+ * @param password the password of the user.
+ * @return {@code true} if the credentials are valid. {@code false}
+ * otherwise.
+ * @throws DAOException if an error happens while checking the credentials.
+ */
+ public boolean checkLogin(String login, String password) throws DAOException {
+ try {
+ final User user = this.get(login);
+
+ final String dbPassword = user.getPassword();
+ final String shaPassword = encodeSha256(password);
+
+ return shaPassword.equals(dbPassword);
+ } catch (IllegalArgumentException iae) {
+ return false;
+ }
+ }
+
+ private final static String encodeSha256(String text) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ final byte[] digested = digest.digest(text.getBytes());
+
+ return hexToString(digested);
+ } catch (NoSuchAlgorithmException e) {
+ LOG.log(Level.SEVERE, "SHA-256 not supported", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final static String hexToString(byte[] hex) {
+ final StringBuilder sb = new StringBuilder();
+
+ for (byte b : hex) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+
+ return sb.toString();
+ }
+
+ private User rowToEntity(ResultSet result) throws SQLException {
+ return new User(
+ result.getString("login"),
+ result.getString("password"),
+ result.getString("role")
+ );
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/entities/Person.java b/src/main/java/es/uvigo/esei/daa/entities/Person.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2f3285a5263ba6f4316d1dfa1eadfd45ae0b2d8
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/entities/Person.java
@@ -0,0 +1,99 @@
+package es.uvigo.esei.daa.entities;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An entity that represents a person.
+ *
+ * @author Miguel Reboiro Jato
+ */
+public class Person {
+ private int id;
+ private String name;
+ private String surname;
+
+ // Constructor needed for the JSON conversion
+ Person() {}
+
+ /**
+ * Constructs a new instance of {@link Person}.
+ *
+ * @param id identifier of the person.
+ * @param name name of the person.
+ * @param surname surname of the person.
+ */
+ public Person(int id, String name, String surname) {
+ this.id = id;
+ this.setName(name);
+ this.setSurname(surname);
+ }
+
+ /**
+ * Returns the identifier of the person.
+ *
+ * @return the identifier of the person.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Returns the name of the person.
+ *
+ * @return the name of the person.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name of this person.
+ *
+ * @param name the new name of the person.
+ * @throws NullPointerException if the {@code name} is {@code null}.
+ */
+ public void setName(String name) {
+ this.name = requireNonNull(name, "Name can't be null");
+ }
+
+ /**
+ * Returns the surname of the person.
+ *
+ * @return the surname of the person.
+ */
+ public String getSurname() {
+ return surname;
+ }
+
+ /**
+ * Set the surname of this person.
+ *
+ * @param surname the new surname of the person.
+ * @throws NullPointerException if the {@code surname} is {@code null}.
+ */
+ public void setSurname(String surname) {
+ this.surname = requireNonNull(surname, "Surname can't be null");
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof Person))
+ return false;
+ Person other = (Person) obj;
+ if (id != other.id)
+ return false;
+ return true;
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/entities/Pet.java b/src/main/java/es/uvigo/esei/daa/entities/Pet.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c17a4221e1eb1921164942a014b5c538085f794
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/entities/Pet.java
@@ -0,0 +1,119 @@
+package es.uvigo.esei.daa.entities;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An entity that represents a person.
+ *
+ * @author Miguel Reboiro Jato
+ */
+public class Pet {
+ private int id;
+ private String name;
+ private int owner;
+ private String typeOfAnimal;
+
+ // Constructor needed for the JSON conversion
+ Pet() {}
+
+ /**
+ * Constructs a new instance of {@link Pet}.
+ *
+ * @param id identifier of the pet.
+ * @param name name of the pet.
+ * @param owner owner of the pet.
+ */
+ public Pet(int id, String name, Integer owner, String typeOfAnimal) {
+ this.id = id;
+ this.setName(name);
+ this.owner=owner;
+ this.setTypeOfAnimal(typeOfAnimal);
+ }
+
+ /**
+ * Returns the identifier of the person.
+ *
+ * @return the identifier of the person.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Returns the name of the pet.
+ *
+ * @return the name of the pet.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the owner of the pet.
+ *
+ * @return the owner of the pet.
+ */
+ public int getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the type of animal.
+ *
+ * @return the type of animal.
+ */
+ public String getTypeOfAnimal() {
+ return typeOfAnimal;
+ }
+
+
+ /**
+ * Set the name of this pet.
+ *
+ * @param name the new name of the pet.
+ * @throws NullPointerException if the {@code name} is {@code null}.
+ */
+ public void setName(String name) {
+ this.name = requireNonNull(name, "Name can't be null");
+ }
+
+
+ public void setOwner(Integer owner) {
+ this.owner = requireNonNull(owner, "Owner can't be null");
+ }
+
+
+ /**
+ * Set the type of animal of this pet.
+ *
+ * @param typeOfAnimal the new type of the pet.
+ * @throws NullPointerException if the {@code typeOfAnimal} is {@code null}.
+ */
+ public void setTypeOfAnimal(String typeOfAnimal) {
+ this.typeOfAnimal = requireNonNull(typeOfAnimal, "Type of animal can't be null");
+ }
+
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof Pet))
+ return false;
+ Pet other = (Pet) obj;
+ if (id != other.id)
+ return false;
+ return true;
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/entities/User.java b/src/main/java/es/uvigo/esei/daa/entities/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb13b8e8c4e42b57595e75222994ac88c40c6210
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/entities/User.java
@@ -0,0 +1,88 @@
+package es.uvigo.esei.daa.entities;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An entity that represents a user.
+ *
+ * @author Miguel Reboiro Jato
+ */
+public class User {
+ private String login;
+ private String password;
+ private String role;
+
+ // Constructor needed for the JSON conversion
+ User() {}
+
+ /**
+ * Constructs a new instance of {@link User}.
+ *
+ * @param login login that identifies the user in the system.
+ * @param password password of the user encoded using SHA-256 and with the
+ * "salt" prefix added.
+ */
+ public User(String login, String password, String role) {
+ this.setLogin(login);
+ this.setPassword(password);
+ this.setRole(role);
+ }
+
+ /**
+ * Returns the login of the user.
+ *
+ * @return the login of the user.
+ */
+ public String getLogin() {
+ return login;
+ }
+
+ /**
+ * Sets the login of the user.
+ *
+ * @param login the login that identifies the user in the system.
+ */
+ public void setLogin(String login) {
+ this.login = requireNonNull(login, "Login can't be null");
+ }
+
+ /**
+ * Returns the password of the user.
+ *
+ * @return the password of the user.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Sets the users password.
+ * @param password the password of the user encoded using SHA-256 and with
+ * the "salt" prefix added.
+ */
+ public void setPassword(String password) {
+ requireNonNull(password, "Password can't be null");
+ if (!password.matches("[a-zA-Z0-9]{64}"))
+ throw new IllegalArgumentException("Password must be a valid SHA-256");
+
+ this.password = password;
+ }
+
+ /**
+ * Returns the role of the user.
+ *
+ * @return the role of the user.
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * Sets the role of the user.
+ *
+ * @param role the role of the user
+ */
+ public void setRole(String role) {
+ this.role = requireNonNull(role, "Role can't be null");
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/rest/PeopleResource.java b/src/main/java/es/uvigo/esei/daa/rest/PeopleResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..09b8834c7b71d5176e012bcd6076d36eb275bb29
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/rest/PeopleResource.java
@@ -0,0 +1,211 @@
+package es.uvigo.esei.daa.rest;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import es.uvigo.esei.daa.dao.DAOException;
+import es.uvigo.esei.daa.dao.PeopleDAO;
+import es.uvigo.esei.daa.entities.Person;
+
+/**
+ * REST resource for managing people.
+ *
+ * @author Miguel Reboiro Jato.
+ */
+@Path("/people")
+@Produces(MediaType.APPLICATION_JSON)
+public class PeopleResource {
+ private final static Logger LOG = Logger.getLogger(PeopleResource.class.getName());
+
+ private final PeopleDAO dao;
+
+ /**
+ * Constructs a new instance of {@link PeopleResource}.
+ */
+ public PeopleResource() {
+ this(new PeopleDAO());
+ }
+
+ // Needed for testing purposes
+ PeopleResource(PeopleDAO dao) {
+ this.dao = dao;
+ }
+
+ /**
+ * Returns a person with the provided identifier.
+ *
+ * @param id the identifier of the person to retrieve.
+ * @return a 200 OK response with a person that has the provided identifier.
+ * If the identifier does not corresponds with any user, a 400 Bad Request
+ * response with an error message will be returned. If an error happens
+ * while retrieving the list, a 500 Internal Server Error response with an
+ * error message will be returned.
+ */
+ @GET
+ @Path("/{id}")
+ public Response get(
+ @PathParam("id") int id
+ ) {
+ try {
+ final Person person = this.dao.get(id);
+
+ return Response.ok(person).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid person id in get method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error getting a person", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * Returns the complete list of people stored in the system.
+ *
+ * @return a 200 OK response with the complete list of people stored in the
+ * system. If an error happens while retrieving the list, a 500 Internal
+ * Server Error response with an error message will be returned.
+ */
+ @GET
+ public Response list() {
+ try {
+ return Response.ok(this.dao.list()).build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error listing people", e);
+ return Response.serverError().entity(e.getMessage()).build();
+ }
+ }
+
+ /**
+ * Creates a new person in the system.
+ *
+ * @param name the name of the new person.
+ * @param surname the surname of the new person.
+ * @return a 200 OK response with a person that has been created. If the
+ * name or the surname are not provided, a 400 Bad Request response with an
+ * error message will be returned. If an error happens while retrieving the
+ * list, a 500 Internal Server Error response with an error message will be
+ * returned.
+ */
+ @POST
+ public Response add(
+ @FormParam("name") String name,
+ @FormParam("surname") String surname
+ ) {
+ try {
+ final Person newPerson = this.dao.add(name, surname);
+
+ return Response.ok(newPerson).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid person id in add method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error adding a person", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * Modifies the data of a person.
+ *
+ * @param id identifier of the person to modify.
+ * @param name the new name of the person.
+ * @param surname the new surname of the person.
+ * @return a 200 OK response with a person that has been modified. If the
+ * identifier does not corresponds with any user or the name or surname are
+ * not provided, a 400 Bad Request response with an error message will be
+ * returned. If an error happens while retrieving the list, a 500 Internal
+ * Server Error response with an error message will be returned.
+ */
+ @PUT
+ @Path("/{id}")
+ public Response modify(
+ @PathParam("id") int id,
+ @FormParam("name") String name,
+ @FormParam("surname") String surname
+ ) {
+ try {
+ final Person modifiedPerson = new Person(id, name, surname);
+ this.dao.modify(modifiedPerson);
+
+ return Response.ok(modifiedPerson).build();
+ } catch (NullPointerException npe) {
+ final String message = String.format("Invalid data for person (name: %s, surname: %s)", name, surname);
+
+ LOG.log(Level.FINE, message);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(message)
+ .build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid person id in modify method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error modifying a person", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * Deletes a person from the system.
+ *
+ * @param id the identifier of the person to be deleted.
+ * @return a 200 OK response with the identifier of the person that has
+ * been deleted. If the identifier does not corresponds with any user, a 400
+ * Bad Request response with an error message will be returned. If an error
+ * happens while retrieving the list, a 500 Internal Server Error response
+ * with an error message will be returned.
+ */
+ @DELETE
+ @Path("/{id}")
+ public Response delete(
+ @PathParam("id") int id
+ ) {
+ try {
+ this.dao.delete(id);
+
+ return Response.ok(id).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid person id in delete method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error deleting a person", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/rest/PetsResource.java b/src/main/java/es/uvigo/esei/daa/rest/PetsResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa46b130c2cacc99a099606bb6c7af1d8d1fb26f
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/rest/PetsResource.java
@@ -0,0 +1,215 @@
+package es.uvigo.esei.daa.rest;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import es.uvigo.esei.daa.dao.DAOException;
+import es.uvigo.esei.daa.dao.PetsDAO;
+import es.uvigo.esei.daa.entities.Pet;
+
+/**
+ * REST resource for managing people.
+ *
+ * @author Miguel Reboiro Jato.
+ */
+@Path("/pets")
+@Produces(MediaType.APPLICATION_JSON)
+public class PetsResource {
+ private final static Logger LOG = Logger.getLogger(PetsResource.class.getName());
+
+ private final PetsDAO dao;
+
+ /**
+ * Constructs a new instance of {@link PetResource}.
+ */
+ public PetsResource() {
+ this(new PetsDAO());
+ }
+
+ // Needed for testing purposes
+ PetsResource(PetsDAO dao) {
+ this.dao = dao;
+ }
+
+ /**
+ * Returns a person with the provided identifier.
+ *
+ * @param id the identifier of the person to retrieve.
+ * @return a 200 OK response with a person that has the provided identifier.
+ * If the identifier does not corresponds with any user, a 400 Bad Request
+ * response with an error message will be returned. If an error happens
+ * while retrieving the list, a 500 Internal Server Error response with an
+ * error message will be returned.
+ */
+ @GET
+ @Path("/{id}")
+ public Response get(
+ @PathParam("id") int id
+ ) {
+ try {
+ final Pet pet = this.dao.get(id);
+
+ return Response.ok(pet).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid pet id in get method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error getting a pey", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * Returns the complete list of pets 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 pets", e);
+ return Response.serverError().entity(e.getMessage()).build();
+ }
+ }
+
+ /**
+ * Creates a new person in the system.
+ *
+ * @param name the name of the new person.
+ * @param owner the owner of the new pet.
+ * @param typeOfAnymal the type of the animal.
+ * @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("owner") Integer owner,
+ @FormParam("typeOfAnimal") String typeOfAnimal
+ ) {
+ try {
+ final Pet newPet = this.dao.add(name, owner, typeOfAnimal);
+
+ return Response.ok(newPet).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid pet id in add method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error adding a pet", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * Modifies the data of a person.
+ *
+ * @param id identifier of the person to modify.
+ * @param name the name of the new person.
+ * @param owner the owner of the new pet.
+ * @param typeOfAnymal the type of the animal
+ * @return a 200 OK response with a person that has been modified. If the
+ * identifier does not corresponds with any user or the name or surname are
+ * not provided, a 400 Bad Request response with an error message will be
+ * returned. If an error happens while retrieving the list, a 500 Internal
+ * Server Error response with an error message will be returned.
+ */
+ @PUT
+ @Path("/{id}")
+ public Response modify(
+ @PathParam("id") int id,
+ @FormParam("name") String name,
+ @FormParam("owner") Integer owner,
+ @FormParam("typeOfAnimal") String typeOfAnimal
+ ) {
+ try {
+ final Pet modifiedPet = new Pet(id, name, owner, typeOfAnimal);
+ this.dao.modify(modifiedPet);
+
+ return Response.ok(modifiedPet).build();
+ } catch (NullPointerException npe) {
+ final String message = String.format("Invalid data for pet (name: %s, owner: %d, type animal: %s)", name, owner, typeOfAnimal);
+
+ LOG.log(Level.FINE, message);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(message)
+ .build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid pet id in modify method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error modifying a pet", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * Deletes a person from the system.
+ *
+ * @param id the identifier of the person to be deleted.
+ * @return a 200 OK response with the identifier of the person that has
+ * been deleted. If the identifier does not corresponds with any user, a 400
+ * Bad Request response with an error message will be returned. If an error
+ * happens while retrieving the list, a 500 Internal Server Error response
+ * with an error message will be returned.
+ */
+ @DELETE
+ @Path("/{id}")
+ public Response delete(
+ @PathParam("id") int id
+ ) {
+ try {
+ this.dao.delete(id);
+
+ return Response.ok(id).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid pet id in delete method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error deleting a pet", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ }
+}
diff --git a/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java b/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..858c52c566cb156e62e80cff75a9961d0c56d100
--- /dev/null
+++ b/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java
@@ -0,0 +1,100 @@
+package es.uvigo.esei.daa.rest;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import es.uvigo.esei.daa.dao.DAOException;
+import es.uvigo.esei.daa.dao.UsersDAO;
+/**
+ * REST resource for managing users.
+ *
+ * @author Miguel Reboiro Jato.
+ */
+@Path("/users")
+@Produces(MediaType.APPLICATION_JSON)
+public class UsersResource {
+ private final static Logger LOG = Logger.getLogger(UsersResource.class.getName());
+
+ private final UsersDAO dao;
+
+ private @Context SecurityContext security;
+
+ /**
+ * Constructs a new instance of {@link UsersResource}.
+ */
+ public UsersResource() {
+ this(new UsersDAO());
+ }
+
+ // Needed for testing purposes
+ UsersResource(UsersDAO dao) {
+ this(dao, null);
+ }
+
+ // Needed for testing purposes
+ UsersResource(UsersDAO dao, SecurityContext security) {
+ this.dao = dao;
+ this.security = security;
+ }
+
+ /**
+ * Returns a user with the provided login.
+ *
+ * @param login the identifier of the user to retrieve.
+ * @return a 200 OK response with an user that has the provided login.
+ * If the request is done without providing the login credentials or using
+ * invalid credentials a 401 Unauthorized response will be returned. If the
+ * credentials are provided and a regular user (i.e. non admin user) tries
+ * to access the data of other user, a 403 Forbidden response will be
+ * returned. If the credentials are OK, but the login does not corresponds
+ * with any user, a 400 Bad Request response with an error message will be
+ * returned. If an error happens while retrieving the list, a 500 Internal
+ * Server Error response with an error message will be returned.
+ */
+ @GET
+ @Path("/{login}")
+ public Response get(
+ @PathParam("login") String login
+ ) {
+ final String loggedUser = getLogin();
+
+ // Each user can only access his or her own data. Only the admin user
+ // can access the data of any user.
+ if (loggedUser.equals(login) || this.isAdmin()) {
+ try {
+ return Response.ok(dao.get(login)).build();
+ } catch (IllegalArgumentException iae) {
+ LOG.log(Level.FINE, "Invalid user login in get method", iae);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(iae.getMessage())
+ .build();
+ } catch (DAOException e) {
+ LOG.log(Level.SEVERE, "Error getting an user", e);
+
+ return Response.serverError()
+ .entity(e.getMessage())
+ .build();
+ }
+ } else {
+ return Response.status(Response.Status.UNAUTHORIZED).build();
+ }
+ }
+
+ private String getLogin() {
+ return this.security.getUserPrincipal().getName();
+ }
+
+ private boolean isAdmin() {
+ return this.security.isUserInRole("ADMIN");
+ }
+}
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a32d8cda80ed2600c9eabbc15ea88849b7f4b3ff
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,75 @@
+
+
+ DAAExample
+
+
+ index.html
+
+
+
+ CorsFilter
+ org.apache.catalina.filters.CorsFilter
+
+ cors.allowed.origins
+ *
+
+
+ cors.allowed.headers
+ Authorization
+
+
+ cors.allowed.methods
+ GET, POST, DELETE, PUT
+
+
+
+ CorsFilter
+ /rest/*
+
+
+
+
+ Protected Area
+ /rest/*
+ PUT
+ DELETE
+ GET
+ POST
+
+
+ ADMIN
+ USER
+
+
+
+
+
+ Admin Area
+ /rest/people/*
+ GET
+ PUT
+ DELETE
+ POST
+
+
+ ADMIN
+
+
+
+
+
+ ADMIN
+
+
+ USER
+
+
+
+ BASIC
+ DAAExample
+
+
\ No newline at end of file
diff --git a/src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java b/src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..43b5809bf3c4fc31f47c7e7d960c7acdba0aa762
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java
@@ -0,0 +1,22 @@
+package es.uvigo.esei.daa;
+
+import static java.util.Collections.unmodifiableSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ApplicationPath;
+
+import es.uvigo.esei.daa.filters.AuthorizationFilter;
+
+@ApplicationPath("/rest/*")
+public class DAAExampleTestApplication extends DAAExampleApplication {
+ @Override
+ public Set> getClasses() {
+ final Set> classes = new HashSet<>(super.getClasses());
+
+ classes.add(AuthorizationFilter.class);
+
+ return unmodifiableSet(classes);
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/dao/PeopleDAOTest.java b/src/test/java/es/uvigo/esei/daa/dao/PeopleDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..68d93675fe9e7004aa2f7a29d50fa33d51da1a3e
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dao/PeopleDAOTest.java
@@ -0,0 +1,136 @@
+package es.uvigo.esei.daa.dao;
+
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.peopleWithout;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import javax.sql.DataSource;
+
+import org.junit.Before;
+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.github.springtestdbunit.DbUnitTestExecutionListener;
+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 {
+ private PeopleDAO dao;
+
+ @Before
+ public void setUp() throws Exception {
+ this.dao = new PeopleDAO();
+ }
+
+ @Test
+ public void testList() throws DAOException {
+ assertThat(this.dao.list(), containsPeopleInAnyOrder(people()));
+ }
+
+ @Test
+ public void testGet() throws DAOException {
+ final Person person = this.dao.get(existentId());
+
+ assertThat(person, is(equalsToPerson(existentPerson())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetNonExistentId() throws DAOException {
+ this.dao.get(nonExistentId());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-delete.xml")
+ public void testDelete() throws DAOException {
+ this.dao.delete(existentId());
+
+ assertThat(this.dao.list(), containsPeopleInAnyOrder(peopleWithout(existentId())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteNonExistentId() throws DAOException {
+ this.dao.delete(nonExistentId());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-modify.xml")
+ public void testModify() throws DAOException {
+ final Person person = existentPerson();
+ person.setName(newName());
+ person.setSurname(newSurname());
+
+ this.dao.modify(person);
+
+ final Person persistentPerson = this.dao.get(person.getId());
+
+ assertThat(persistentPerson, is(equalsToPerson(person)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyNonExistentId() throws DAOException {
+ this.dao.modify(nonExistentPerson());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyNullPerson() throws DAOException {
+ this.dao.modify(null);
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-add.xml")
+ public void testAdd() throws DAOException {
+ final Person person = this.dao.add(newName(), newSurname());
+
+ assertThat(person, is(equalsToPerson(newPerson())));
+
+ final Person persistentPerson = this.dao.get(person.getId());
+
+ assertThat(persistentPerson, is(equalsToPerson(newPerson())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullName() throws DAOException {
+ this.dao.add(null, newSurname());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullSurname() throws DAOException {
+ this.dao.add(newName(), null);
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/dao/PeopleDAOUnitTest.java b/src/test/java/es/uvigo/esei/daa/dao/PeopleDAOUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..94ac74b92a0cf20ef651f4f7e4cdd7b908f0ee50
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dao/PeopleDAOUnitTest.java
@@ -0,0 +1,259 @@
+package es.uvigo.esei.daa.dao;
+
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.reset;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.sql.SQLException;
+
+import org.junit.Test;
+
+import com.mysql.jdbc.Statement;
+
+import es.uvigo.esei.daa.entities.Person;
+import es.uvigo.esei.daa.util.DatabaseQueryUnitTest;
+
+public class PeopleDAOUnitTest extends DatabaseQueryUnitTest {
+ @Test
+ public void testList() throws Exception {
+ final Person[] people = people();
+
+ for (Person person : people) {
+ expectPersonRow(person);
+ }
+ expect(result.next()).andReturn(false);
+ result.close();
+
+ replayAll();
+ final PeopleDAO peopleDAO = new PeopleDAO();
+
+ assertThat(peopleDAO.list(), containsPeopleInAnyOrder(people));
+ }
+
+ @Test(expected = DAOException.class)
+ public void testListUnexpectedException() throws Exception {
+ expect(result.next()).andThrow(new SQLException());
+ result.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.list();
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ final Person existentPerson = existentPerson();
+
+ expectPersonRow(existentPerson);
+ result.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+
+ assertThat(peopleDAO.get(existentId()), is(equalTo(existentPerson)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetMissing() throws Exception {
+ expect(result.next()).andReturn(false);
+ result.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.get(existentId());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testGetUnexpectedException() throws Exception {
+ expect(result.next()).andThrow(new SQLException());
+ result.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.get(existentId());
+ }
+
+ @Test
+ public void testAdd() throws Exception {
+ final Person person = newPerson();
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(Statement.RETURN_GENERATED_KEYS)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andReturn(1);
+ expect(statement.getGeneratedKeys()).andReturn(result);
+
+ // Key retrieval
+ expect(result.next()).andReturn(true);
+ expect(result.getInt(1)).andReturn(person.getId());
+ connection.close();
+ result.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ final Person newPerson = peopleDAO.add(person.getName(), person.getSurname());
+
+ assertThat(newPerson, is(equalsToPerson(person)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullName() throws Exception {
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+
+ resetAll(); // No expectations
+
+ peopleDAO.add(null, newSurname());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullSurname() throws Exception {
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+
+ resetAll(); // No expectations
+
+ peopleDAO.add(newName(), null);
+ }
+
+ @Test(expected = DAOException.class)
+ public void testAddZeroUpdatedRows() throws Exception {
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(1)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andReturn(0);
+ connection.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.add(newName(), newSurname());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testAddNoGeneratedKey() throws Exception {
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(1)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andReturn(1);
+ expect(statement.getGeneratedKeys()).andReturn(result);
+ expect(result.next()).andReturn(false);
+ result.close();
+ connection.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.add(newName(), newSurname());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testAddUnexpectedException() throws Exception {
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(1)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andThrow(new SQLException());
+ connection.close();
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.add(newName(), newSurname());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ expect(statement.executeUpdate()).andReturn(1);
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.delete(existentId());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteInvalidId() throws Exception {
+ expect(statement.executeUpdate()).andReturn(0);
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.delete(existentId());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testDeleteUnexpectedException() throws Exception {
+ expect(statement.executeUpdate()).andThrow(new SQLException());
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.delete(existentId());
+ }
+
+ @Test
+ public void testModify() throws Exception {
+ expect(statement.executeUpdate()).andReturn(1);
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.modify(existentPerson());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyNullPerson() throws Exception {
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+
+ resetAll(); // No expectations
+
+ peopleDAO.modify(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyZeroUpdatedRows() throws Exception {
+ expect(statement.executeUpdate()).andReturn(0);
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.modify(existentPerson());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testModifyUnexpectedException() throws Exception {
+ expect(statement.executeUpdate()).andThrow(new SQLException());
+
+ replayAll();
+
+ final PeopleDAO peopleDAO = new PeopleDAO();
+ peopleDAO.modify(existentPerson());
+ }
+
+ private void expectPersonRow(Person person) throws SQLException {
+ expect(result.next()).andReturn(true);
+ expect(result.getInt("id")).andReturn(person.getId());
+ expect(result.getString("name")).andReturn(person.getName());
+ expect(result.getString("surname")).andReturn(person.getSurname());
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/dao/PetsDAOTest.java b/src/test/java/es/uvigo/esei/daa/dao/PetsDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a97fb0d4c3ebca12b6f3d82047cc687f6bdb5df
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dao/PetsDAOTest.java
@@ -0,0 +1,143 @@
+package es.uvigo.esei.daa.dao;
+
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newName;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newOwner;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newTypeOfAnimal;
+import static es.uvigo.esei.daa.dataset.PetsDataset.nonExistentId;
+import static es.uvigo.esei.daa.dataset.PetsDataset.nonExistentPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.pets;
+import static es.uvigo.esei.daa.dataset.PetsDataset.petsWithout;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.containsPetsInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.equalsToPet;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import javax.sql.DataSource;
+
+import org.junit.Before;
+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.github.springtestdbunit.DbUnitTestExecutionListener;
+import com.github.springtestdbunit.annotation.DatabaseSetup;
+import com.github.springtestdbunit.annotation.ExpectedDatabase;
+
+import es.uvigo.esei.daa.entities.Pet;
+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 PetsDAOTest {
+ private PetsDAO dao;
+
+ @Before
+ public void setUp() throws Exception {
+ this.dao = new PetsDAO();
+ }
+
+ @Test
+ public void testList() throws DAOException {
+ assertThat(this.dao.list(), containsPetsInAnyOrder(pets()));
+ }
+
+ @Test
+ public void testGet() throws DAOException {
+ final Pet pet = this.dao.get(existentId());
+
+ assertThat(pet, is(equalsToPet(existentPet())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetNonExistentId() throws DAOException {
+ this.dao.get(nonExistentId());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-delete-pets.xml")
+ public void testDelete() throws DAOException {
+ this.dao.delete(existentId());
+
+ assertThat(this.dao.list(), containsPetsInAnyOrder(petsWithout(existentId())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteNonExistentId() throws DAOException {
+ this.dao.delete(nonExistentId());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-modify-pets.xml")
+ public void testModify() throws DAOException {
+ final Pet pet = existentPet();
+ pet.setName(newName());
+ pet.setOwner(newOwner());
+ pet.setTypeOfAnimal(newTypeOfAnimal());
+
+ this.dao.modify(pet);
+
+ final Pet persistentPet = this.dao.get(pet.getId());
+
+ assertThat(persistentPet, is(equalsToPet(pet)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyNonExistentId() throws DAOException {
+ this.dao.modify(nonExistentPet());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyNullPet() throws DAOException {
+ this.dao.modify(null);
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-add-pets.xml")
+ public void testAdd() throws DAOException {
+ final Pet pet = this.dao.add(newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(pet, is(equalsToPet(newPet())));
+
+ final Pet persistentPet = this.dao.get(pet.getId());
+
+ assertThat(persistentPet, is(equalsToPet(newPet())));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullName() throws DAOException {
+ this.dao.add(null, newOwner(), newTypeOfAnimal());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullOwner() throws DAOException {
+ this.dao.add(newName(), null, newTypeOfAnimal());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullTypeOfAnimal() throws DAOException {
+ this.dao.add(newName(), newOwner(), null);
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/dao/PetsDAOUnitTest.java b/src/test/java/es/uvigo/esei/daa/dao/PetsDAOUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec137887e8d85d2875415c077d93388095d6e119
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dao/PetsDAOUnitTest.java
@@ -0,0 +1,278 @@
+package es.uvigo.esei.daa.dao;
+
+
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newName;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newOwner;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newTypeOfAnimal;
+import static es.uvigo.esei.daa.dataset.PetsDataset.pets;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.containsPetsInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.equalsToPet;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.reset;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.sql.SQLException;
+
+import org.junit.Test;
+
+import com.mysql.jdbc.Statement;
+
+
+import es.uvigo.esei.daa.entities.Pet;
+import es.uvigo.esei.daa.util.DatabaseQueryUnitTest;
+
+public class PetsDAOUnitTest extends DatabaseQueryUnitTest{
+
+ @Test
+ public void testList() throws Exception {
+ final Pet[] pets = pets();
+
+ for (Pet pet : pets) {
+ expectPetRow(pet);
+ }
+ expect(result.next()).andReturn(false);
+ result.close();
+
+ replayAll();
+ final PetsDAO petsDAO = new PetsDAO();
+
+ assertThat(petsDAO.list(), containsPetsInAnyOrder(pets));
+ }
+
+ @Test(expected = DAOException.class)
+ public void testListUnexpectedException() throws Exception {
+ expect(result.next()).andThrow(new SQLException());
+ result.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.list();
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ final Pet existentPet = existentPet();
+
+ expectPetRow(existentPet);
+ result.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+
+ assertThat(petsDAO.get(existentId()), is(equalTo(existentPet)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetMissing() throws Exception {
+ expect(result.next()).andReturn(false);
+ result.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.get(existentId());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testGetUnexpectedException() throws Exception {
+ expect(result.next()).andThrow(new SQLException());
+ result.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.get(existentId());
+ }
+
+ @Test
+ public void testAdd() throws Exception {
+ final Pet pet = newPet();
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(Statement.RETURN_GENERATED_KEYS)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andReturn(1);
+ expect(statement.getGeneratedKeys()).andReturn(result);
+
+ // Key retrieval
+ expect(result.next()).andReturn(true);
+ expect(result.getInt(1)).andReturn(pet.getId());
+ connection.close();
+ result.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ final Pet newPet = petsDAO.add(pet.getName(), pet.getOwner(),pet.getTypeOfAnimal());
+
+ assertThat(newPet, is(equalsToPet(pet)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullName() throws Exception {
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+
+ resetAll(); // No expectations
+
+ petsDAO.add(null, newOwner(), newTypeOfAnimal());
+
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullOwner() throws Exception {
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+
+ resetAll(); // No expectations
+
+ petsDAO.add(newName(), null, newTypeOfAnimal());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddNullTypeOfAnimal() throws Exception {
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+
+ resetAll(); // No expectations
+
+ petsDAO.add(newName(), newOwner(), null);
+ }
+
+ @Test(expected = DAOException.class)
+ public void testAddZeroUpdatedRows() throws Exception {
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(1)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andReturn(0);
+ connection.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.add(newName(), newOwner(), newTypeOfAnimal());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testAddNoGeneratedKey() throws Exception {
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(1)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andReturn(1);
+ expect(statement.getGeneratedKeys()).andReturn(result);
+ expect(result.next()).andReturn(false);
+ result.close();
+ connection.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.add(newName(), newOwner(), newTypeOfAnimal());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testAddUnexpectedException() throws Exception {
+ reset(connection);
+ expect(connection.prepareStatement(anyString(), eq(1)))
+ .andReturn(statement);
+ expect(statement.executeUpdate()).andThrow(new SQLException());
+ connection.close();
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.add(newName(), newOwner(), newTypeOfAnimal());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ expect(statement.executeUpdate()).andReturn(1);
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.delete(existentId());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDeleteInvalidId() throws Exception {
+ expect(statement.executeUpdate()).andReturn(0);
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.delete(existentId());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testDeleteUnexpectedException() throws Exception {
+ expect(statement.executeUpdate()).andThrow(new SQLException());
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.delete(existentId());
+ }
+
+ @Test
+ public void testModify() throws Exception {
+ expect(statement.executeUpdate()).andReturn(1);
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.modify(existentPet());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyNullPet() throws Exception {
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+
+ resetAll(); // No expectations
+
+ petsDAO.modify(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testModifyZeroUpdatedRows() throws Exception {
+ expect(statement.executeUpdate()).andReturn(0);
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.modify(existentPet());
+ }
+
+ @Test(expected = DAOException.class)
+ public void testModifyUnexpectedException() throws Exception {
+ expect(statement.executeUpdate()).andThrow(new SQLException());
+
+ replayAll();
+
+ final PetsDAO petsDAO = new PetsDAO();
+ petsDAO.modify(existentPet());
+ }
+
+ private void expectPetRow(Pet pet) throws SQLException {
+ expect(result.next()).andReturn(true);
+ expect(result.getInt("id")).andReturn(pet.getId());
+ expect(result.getString("name")).andReturn(pet.getName());
+ expect(result.getInt("owner")).andReturn(pet.getOwner());
+ expect(result.getString("typeOfAnimal")).andReturn(pet.getTypeOfAnimal());
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/es/uvigo/esei/daa/dataset/PeopleDataset.java b/src/test/java/es/uvigo/esei/daa/dataset/PeopleDataset.java
new file mode 100644
index 0000000000000000000000000000000000000000..764a44f32ca073bfff43697da7e915ab6c0b4d9b
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dataset/PeopleDataset.java
@@ -0,0 +1,74 @@
+package es.uvigo.esei.daa.dataset;
+
+import static java.util.Arrays.binarySearch;
+import static java.util.Arrays.stream;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import es.uvigo.esei.daa.entities.Person;
+
+public final class PeopleDataset {
+ private PeopleDataset() {}
+
+ public static Person[] people() {
+ return new Person[] {
+ new Person(1, "Antón", "Álvarez"),
+ new Person(2, "Ana", "Amargo"),
+ new Person(3, "Manuel", "Martínez"),
+ new Person(4, "María", "Márquez"),
+ new Person(5, "Lorenzo", "López"),
+ new Person(6, "Laura", "Laredo"),
+ new Person(7, "Perico", "Palotes"),
+ new Person(8, "Patricia", "Pérez"),
+ new Person(9, "Julia", "Justa"),
+ new Person(10, "Juan", "Jiménez")
+ };
+ }
+
+ public static Person[] peopleWithout(int ... ids) {
+ Arrays.sort(ids);
+
+ final Predicate hasValidId = person ->
+ binarySearch(ids, person.getId()) < 0;
+
+ return stream(people())
+ .filter(hasValidId)
+ .toArray(Person[]::new);
+ }
+
+ public static Person person(int id) {
+ return stream(people())
+ .filter(person -> person.getId() == id)
+ .findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public static int existentId() {
+ return 5;
+ }
+
+ public static int nonExistentId() {
+ return 1234;
+ }
+
+ public static Person existentPerson() {
+ return person(existentId());
+ }
+
+ public static Person nonExistentPerson() {
+ return new Person(nonExistentId(), "Jane", "Smith");
+ }
+
+ public static String newName() {
+ return "John";
+ }
+
+ public static String newSurname() {
+ return "Doe";
+ }
+
+ public static Person newPerson() {
+ return new Person(people().length + 1, newName(), newSurname());
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/dataset/PetsDataset.java b/src/test/java/es/uvigo/esei/daa/dataset/PetsDataset.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d0bc83b2a0789688f1a8648cc7a88faba015687
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dataset/PetsDataset.java
@@ -0,0 +1,82 @@
+package es.uvigo.esei.daa.dataset;
+
+import static java.util.Arrays.binarySearch;
+import static java.util.Arrays.stream;
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import org.junit.Test;
+
+import es.uvigo.esei.daa.entities.Pet;
+
+public class PetsDataset {
+private PetsDataset() {}
+
+ public static Pet[] pets() {
+ return new Pet[] {
+ new Pet(1, "Antón", 1,"Perro"),
+ new Pet(2, "Ana", 2,"Gato"),
+ new Pet(3, "Manuel", 3,"Cobaya"),
+ new Pet(4, "María", 4,"Zorro"),
+ new Pet(5, "Lorenzo", 5,"Rata"),
+ new Pet(6, "Laura", 6,"Pitón"),
+ new Pet(7, "Perico", 7,"Langostino"),
+ new Pet(8, "Patricia", 8,"Avutarda"),
+ new Pet(9, "Julia", 9,"Tucán"),
+ new Pet(10, "Juan", 10,"Tigre")
+ };
+ }
+
+ public static Pet[] petsWithout(int ... ids) {
+ Arrays.sort(ids);
+
+ final Predicate hasValidId = pet ->
+ binarySearch(ids, pet.getId()) < 0;
+
+ return stream(pets())
+ .filter(hasValidId)
+ .toArray(Pet[]::new);
+ }
+
+ public static Pet pet(int id) {
+ return stream(pets())
+ .filter(pet -> pet.getId() == id)
+ .findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public static int existentId() {
+ return 5;
+ }
+
+ public static int nonExistentId() {
+ return 1234;
+ }
+
+ public static Pet existentPet() {
+ return pet(existentId());
+ }
+
+ public static Pet nonExistentPet() {
+ return new Pet(nonExistentId(), "Jane", 1,"Zebra");
+ }
+
+ public static String newName() {
+ return "John";
+ }
+
+ public static Integer newOwner() {
+ return 1;
+ }
+
+ public static String newTypeOfAnimal() {
+ return "Perro";
+ }
+
+ public static Pet newPet() {
+ return new Pet(pets().length + 1, newName(), newOwner(), newTypeOfAnimal());
+ }
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java b/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java
new file mode 100644
index 0000000000000000000000000000000000000000..82cbcdeec1e23abcc76b0832958f33bce13c02df
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java
@@ -0,0 +1,38 @@
+package es.uvigo.esei.daa.dataset;
+
+import java.util.Arrays;
+import java.util.Base64;
+
+import es.uvigo.esei.daa.entities.User;
+
+public final class UsersDataset {
+ private UsersDataset() {}
+
+ public static User[] users() {
+ return new User[] {
+ new User(adminLogin(), "713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca", "ADMIN"),
+ new User(normalLogin(), "7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83", "USER")
+ };
+ }
+
+ public static User user(String login) {
+ return Arrays.stream(users())
+ .filter(user -> user.getLogin().equals(login))
+ .findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public static String adminLogin() {
+ return "admin";
+ }
+
+ public static String normalLogin() {
+ return "normal";
+ }
+
+ public static String userToken(String login) {
+ final String chain = login + ":" + login + "pass";
+
+ return Base64.getEncoder().encodeToString(chain.getBytes());
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/entities/PersonUnitTest.java b/src/test/java/es/uvigo/esei/daa/entities/PersonUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a648f5ddbaa0f9221a2ccfbed9e2a7d295c51a4d
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/entities/PersonUnitTest.java
@@ -0,0 +1,93 @@
+package es.uvigo.esei.daa.entities;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+
+public class PersonUnitTest {
+ @Test
+ public void testPersonIntStringString() {
+ final int id = 1;
+ final String name = "John";
+ final String surname = "Doe";
+
+ final Person person = new Person(id, name, surname);
+
+ assertThat(person.getId(), is(equalTo(id)));
+ assertThat(person.getName(), is(equalTo(name)));
+ assertThat(person.getSurname(), is(equalTo(surname)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPersonIntStringStringNullName() {
+ new Person(1, null, "Doe");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPersonIntStringStringNullSurname() {
+ new Person(1, "John", null);
+ }
+
+ @Test
+ public void testSetName() {
+ final int id = 1;
+ final String surname = "Doe";
+
+ final Person person = new Person(id, "John", surname);
+ person.setName("Juan");
+
+ assertThat(person.getId(), is(equalTo(id)));
+ assertThat(person.getName(), is(equalTo("Juan")));
+ assertThat(person.getSurname(), is(equalTo(surname)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testSetNullName() {
+ final Person person = new Person(1, "John", "Doe");
+
+ person.setName(null);
+ }
+
+ @Test
+ public void testSetSurname() {
+ final int id = 1;
+ final String name = "John";
+
+ final Person person = new Person(id, name, "Doe");
+ person.setSurname("Dolores");
+
+ assertThat(person.getId(), is(equalTo(id)));
+ assertThat(person.getName(), is(equalTo(name)));
+ assertThat(person.getSurname(), is(equalTo("Dolores")));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testSetNullSurname() {
+ final Person person = new Person(1, "John", "Doe");
+
+ person.setSurname(null);
+ }
+
+ @Test
+ public void testEqualsObject() {
+ final Person personA = new Person(1, "Name A", "Surname A");
+ final Person personB = new Person(1, "Name B", "Surname B");
+
+ assertTrue(personA.equals(personB));
+ }
+
+ @Test
+ public void testEqualsHashcode() {
+ EqualsVerifier.forClass(Person.class)
+ .withIgnoredFields("name", "surname")
+ .suppress(Warning.STRICT_INHERITANCE)
+ .suppress(Warning.NONFINAL_FIELDS)
+ .verify();
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/entities/PetUnitTest.java b/src/test/java/es/uvigo/esei/daa/entities/PetUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..603b36b5393da96e651238c4988d4826fd933827
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/entities/PetUnitTest.java
@@ -0,0 +1,106 @@
+package es.uvigo.esei.daa.entities;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+
+public class PetUnitTest {
+
+ @Test
+ public void testPetIntStringIntString() {
+ final int id = 1;
+ final String name = "John";
+ final int owner = 1;
+ final String typeOfAnimal = "Dog";
+
+ final Pet pet = new Pet(id, name, owner,typeOfAnimal);
+
+ assertThat(pet.getId(), is(equalTo(id)));
+ assertThat(pet.getName(), is(equalTo(name)));
+ assertThat(pet.getOwner(), is(equalTo(owner)));
+ assertThat(pet.getTypeOfAnimal(), is(equalTo(typeOfAnimal)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPetIntStringIntStringNullName() {
+ new Pet(1, null, 1, "Dog");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPetIntStringIntStringNullOwner() {
+ new Pet(1, "John", null, "Dog");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPetIntStringIntStringNullTypeOfAnimal() {
+ new Pet(1, "John", null, null);
+ }
+
+ @Test
+ public void testSetName() {
+ final int id = 1;
+ final int owner = 1;
+ final String typeOfAnimal = "Dog";
+
+ final Pet pet = new Pet(id, "Juan", owner, typeOfAnimal);
+ pet.setName("John");
+
+ assertThat(pet.getId(), is(equalTo(id)));
+ assertThat(pet.getName(), is(equalTo("John")));
+ assertThat(pet.getOwner(), is(equalTo(owner)));
+ assertThat(pet.getTypeOfAnimal(), is(equalTo(typeOfAnimal)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testSetNullName() {
+ final Pet pet = new Pet(1, "John", 1,"Dog");
+
+ pet.setName(null);
+ }
+
+ @Test
+ public void testSetTypeOfAnimal() {
+ final int id = 1;
+ final int owner=1;
+ final String name = "John";
+
+ final Pet pet = new Pet(id, name, owner,"Cat");
+ pet.setTypeOfAnimal("Dog");
+
+ assertThat(pet.getId(), is(equalTo(id)));
+ assertThat(pet.getName(), is(equalTo(name)));
+ assertThat(pet.getOwner(), is(equalTo(owner)));
+ assertThat(pet.getTypeOfAnimal(), is(equalTo("Dog")));
+
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testSetNullTypeOfAnimal() {
+ final Pet pet = new Pet(1, "John", 1,"Dog");
+
+ pet.setTypeOfAnimal(null);
+ }
+
+ @Test
+ public void testEqualsObject() {
+ final Pet petA = new Pet(1, "Name A", 1, "Type Of Animal A");
+ final Pet petB = new Pet(1, "Name B", 1, "Type Of Animal B");
+
+ assertTrue(petA.equals(petB));
+ }
+
+ @Test
+ public void testEqualsHashcode() {
+ EqualsVerifier.forClass(Pet.class)
+ .withIgnoredFields("name", "owner", "typeOfAnimal")
+ .suppress(Warning.STRICT_INHERITANCE)
+ .suppress(Warning.NONFINAL_FIELDS)
+ .verify();
+ }
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java b/src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..e559bc1d28b5967e408a7642abd2fe15353b28d8
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java
@@ -0,0 +1,126 @@
+package es.uvigo.esei.daa.filters;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Base64;
+import java.util.List;
+
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.ext.Provider;
+
+import es.uvigo.esei.daa.dao.DAOException;
+import es.uvigo.esei.daa.dao.UsersDAO;
+import es.uvigo.esei.daa.entities.User;
+
+/**
+ * This performs the Basic HTTP authentication following (almost) the same
+ * rules as the defined in the web.xml file.
+ *
+ * @author Miguel Reboiro Jato
+ */
+@Provider
+@Priority(Priorities.AUTHENTICATION)
+public class AuthorizationFilter implements ContainerRequestFilter {
+ private final UsersDAO dao;
+
+ public AuthorizationFilter() {
+ this.dao = new UsersDAO();
+ }
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+ // Get the authentication passed in HTTP headers parameters
+ final String auth = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
+
+ if (auth == null) {
+ requestContext.abortWith(createResponse());
+ } else {
+ final byte[] decodedToken = Base64.getDecoder()
+ .decode(auth.substring(6));
+
+ final String userColonPass = new String(decodedToken);
+ final String[] userPass = userColonPass.split(":", 2);
+
+ if (userPass.length == 2) {
+ try {
+ if (this.dao.checkLogin(userPass[0], userPass[1])) {
+ final User user = this.dao.get(userPass[0]);
+
+ if (isPeoplePath(requestContext) && !user.getRole().equals("ADMIN")) {
+ requestContext.abortWith(createResponse());
+ } else if (isPetsPath(requestContext) && !user.getRole().equals("ADMIN")) {
+ requestContext.abortWith(createResponse());
+ }
+ else {
+ requestContext.setSecurityContext(new UserSecurityContext(user));
+ }
+ } else {
+ requestContext.abortWith(createResponse());
+ }
+ } catch (DAOException e) {
+ requestContext.abortWith(createResponse());
+ }
+ } else {
+ requestContext.abortWith(createResponse());
+ }
+ }
+ }
+
+ private static boolean isPeoplePath(ContainerRequestContext context) {
+ final List pathSegments = context.getUriInfo().getPathSegments();
+ return !pathSegments.isEmpty() && pathSegments.get(0).getPath().equals("people");
+ }
+
+ private static boolean isPetsPath(ContainerRequestContext context) {
+ final List pathSegments = context.getUriInfo().getPathSegments();
+ return !pathSegments.isEmpty() && pathSegments.get(0).getPath().equals("pets");
+ }
+
+ private static Response createResponse() {
+ return Response.status(Status.UNAUTHORIZED)
+ .header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"DAAExample\"")
+ .entity("Page requires login.")
+ .build();
+ }
+
+ private static final class UserSecurityContext implements SecurityContext {
+ private final User user;
+
+ private UserSecurityContext(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return user.getRole().equals(role);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return new Principal() {
+ @Override
+ public String getName() {
+ return user.getLogin();
+ }
+ };
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return SecurityContext.BASIC_AUTH;
+ }
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextBinding.java b/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextBinding.java
new file mode 100644
index 0000000000000000000000000000000000000000..e22da5ee48c5a1e173c0b13e839459a1c07bc326
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextBinding.java
@@ -0,0 +1,18 @@
+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 {}
+}
diff --git a/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextBindings.java b/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextBindings.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b728ff2058a532b83e81dbbc58cce485d280787
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextBindings.java
@@ -0,0 +1,12 @@
+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();
+}
diff --git a/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextJndiBindingTestExecutionListener.java b/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextJndiBindingTestExecutionListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..0708cc00d9a041a4463988c5c9fe6f32676baa83
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/listeners/ApplicationContextJndiBindingTestExecutionListener.java
@@ -0,0 +1,44 @@
+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;
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/listeners/DbManagement.java b/src/test/java/es/uvigo/esei/daa/listeners/DbManagement.java
new file mode 100644
index 0000000000000000000000000000000000000000..13bb496c9d48692f3a2fe6fc9af9a3bb6ad91ddc
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/listeners/DbManagement.java
@@ -0,0 +1,14 @@
+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;
+}
diff --git a/src/test/java/es/uvigo/esei/daa/listeners/DbManagementAction.java b/src/test/java/es/uvigo/esei/daa/listeners/DbManagementAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e23a1c5e9dd556be92f66245647b774c8af3b49
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/listeners/DbManagementAction.java
@@ -0,0 +1,5 @@
+package es.uvigo.esei.daa.listeners;
+
+public enum DbManagementAction {
+ DROP_CREATE_DROP, CREATE_DROP, ONLY_CREATE, ONLY_DROP;
+}
diff --git a/src/test/java/es/uvigo/esei/daa/listeners/DbManagementTestExecutionListener.java b/src/test/java/es/uvigo/esei/daa/listeners/DbManagementTestExecutionListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ec05f6b5833431d0b22c052f466f6e72dc85075
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/listeners/DbManagementTestExecutionListener.java
@@ -0,0 +1,113 @@
+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)));
+ }
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/matchers/HasHttpStatus.java b/src/test/java/es/uvigo/esei/daa/matchers/HasHttpStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..8da8cb196f99243c5dbf04e183fb232ad475f2be
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/matchers/HasHttpStatus.java
@@ -0,0 +1,95 @@
+/*
+ * #%L
+ * PanDrugsDB Backend
+ * %%
+ * Copyright (C) 2015 Fátima Al-Shahrour, Elena Piñeiro, Daniel Glez-Peña and Miguel Reboiro-Jato
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+package es.uvigo.esei.daa.matchers;
+
+import static java.util.Objects.requireNonNull;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.Response.StatusType;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class HasHttpStatus extends TypeSafeMatcher {
+ private final StatusType expectedStatus;
+
+ public HasHttpStatus(StatusType expectedStatus) {
+ this.expectedStatus = requireNonNull(expectedStatus);
+ }
+
+ public HasHttpStatus(int expectedStatus) {
+ this(Status.fromStatusCode(expectedStatus));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(this.expectedStatus);
+ }
+
+ @Override
+ protected void describeMismatchSafely(Response item, Description mismatchDescription) {
+ mismatchDescription.appendText("was ").appendValue(item.getStatusInfo());
+ }
+
+ @Override
+ protected boolean matchesSafely(Response item) {
+ return item != null && expectedStatus.getStatusCode() == item.getStatusInfo().getStatusCode();
+ }
+
+ @Factory
+ public static Matcher hasHttpStatus(StatusType expectedStatus) {
+ return new HasHttpStatus(expectedStatus);
+ }
+
+ @Factory
+ public static Matcher hasHttpStatus(int expectedStatus) {
+ return new HasHttpStatus(expectedStatus);
+ }
+
+ @Factory
+ public static Matcher hasOkStatus() {
+ return new HasHttpStatus(Response.Status.OK);
+ }
+
+ @Factory
+ public static Matcher hasBadRequestStatus() {
+ return new HasHttpStatus(Response.Status.BAD_REQUEST);
+ }
+
+ @Factory
+ public static Matcher hasInternalServerErrorStatus() {
+ return new HasHttpStatus(Response.Status.INTERNAL_SERVER_ERROR);
+ }
+
+ @Factory
+ public static Matcher hasUnauthorized() {
+ return new HasHttpStatus(Response.Status.UNAUTHORIZED);
+ }
+
+ @Factory
+ public static Matcher hasForbidden() {
+ return new HasHttpStatus(Response.Status.FORBIDDEN);
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToEntity.java b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..a85dbc5c84f3c2eb2d182b7dfffc73a851be2123
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToEntity.java
@@ -0,0 +1,364 @@
+package es.uvigo.esei.daa.matchers;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.StreamSupport;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * An abstract {@link Matcher} that can be used to create new matchers that
+ * compare entities by their attributes.
+ *
+ * @author Miguel Reboiro Jato
+ *
+ * @param the type of the entities to be matched.
+ */
+public abstract class IsEqualToEntity extends TypeSafeMatcher {
+ /**
+ * The expected entity.
+ */
+ protected final T expected;
+
+ private Consumer describeTo;
+
+ /**
+ * Constructs a new instance of {@link IsEqualToEntity}.
+ *
+ * @param entity the expected tentity.
+ */
+ public IsEqualToEntity(final T entity) {
+ this.expected = requireNonNull(entity);
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ if (this.describeTo != null)
+ this.describeTo.accept(description);
+ }
+
+ /**
+ * Adds a new description using the template:
+ *
+ * {@code entity with value '' for }
+ *
+ *
+ * @param attribute the name of the attribute compared.
+ * @param expected the expected value.
+ */
+ protected void addTemplatedDescription(final String attribute, final Object expected) {
+ this.describeTo = d -> d.appendText(String.format(
+ "%s entity with value '%s' for %s",
+ this.expected.getClass().getSimpleName(),
+ expected, attribute
+ ));
+ }
+
+ /**
+ * Adds as the description of this matcher the
+ * {@link Matcher#describeTo(Description)} method of other matcher.
+ *
+ * @param matcher the matcher whose description will be used.
+ */
+ protected void addMatcherDescription(final Matcher> matcher) {
+ this.describeTo = matcher::describeTo;
+ }
+
+ /**
+ * Cleans the current description.
+ */
+ protected void clearDescribeTo() {
+ this.describeTo = null;
+ }
+
+ protected boolean checkAttribute(
+ final String attribute,
+ final Function getter, final T actual,
+ final Function> matcherFactory
+ ) {
+ final R expectedValue = getter.apply(this.expected);
+ final R actualValue = getter.apply(actual);
+
+ if (expectedValue == null && actualValue == null) {
+ return true;
+ } else if (expectedValue == null || actualValue == null) {
+ this.addTemplatedDescription(attribute, expectedValue);
+ return false;
+ } else {
+ final Matcher matcher = matcherFactory.apply(expectedValue);
+ if (matcher.matches(actualValue)) {
+ return true;
+ } else {
+ this.addMatcherDescription(matcher);
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Compares the expected and the actual value of an array attribute. The
+ * elements of the attribute will be checked using a custom matcher.
+ * If the comparison fails, the description of the error will be updated.
+ *
+ * @param attribute the name of the attribute compared.
+ * @param getter the getter function of the attribute.
+ * @param actual the actual entity being compared to the expected entity.
+ * @param matcherFactory a function that creates a matcher for the expected
+ * array values.
+ * @param type of the value returned by the getter.
+ * @return {@code true} if the value of the expected and actual attributes
+ * are equals and {@code false} otherwise. If the result is {@code false},
+ * the current description will be updated.
+ */
+ protected boolean checkArrayAttribute(
+ final String attribute,
+ final Function getter, final T actual,
+ final Function>> matcherFactory
+ ) {
+ final R[] expectedValue = getter.apply(this.expected);
+ final R[] actualValue = getter.apply(actual);
+
+ if (expectedValue == null && actualValue == null) {
+ return true;
+ } else if (expectedValue == null || actualValue == null) {
+ this.addTemplatedDescription(attribute, expectedValue);
+ return false;
+ } else {
+ final Matcher> matcher =
+ matcherFactory.apply(expectedValue);
+
+ if (matcher.matches(asList(actualValue))) {
+ return true;
+ } else {
+ this.addMatcherDescription(matcher);
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Compares the expected and the actual value of an iterable attribute. The
+ * elements of the attribute will be checked using a custom matcher.
+ * If the comparison fails, the description of the error will be updated.
+ *
+ * @param attribute the name of the attribute compared.
+ * @param getter the getter function of the attribute.
+ * @param actual the actual entity being compared to the expected entity.
+ * @param matcherFactory a function that creates a matcher for the expected
+ * iterable values.
+ * @param type of the value returned by the getter.
+ * @return {@code true} if the value of the expected and actual attributes
+ * are equals and {@code false} otherwise. If the result is {@code false},
+ * the current description will be updated.
+ */
+ protected boolean checkIterableAttribute(
+ final String attribute,
+ final Function> getter, final T actual,
+ final Function, Matcher>> matcherFactory
+ ) {
+ final Iterable expectedValue = getter.apply(this.expected);
+ final Iterable actualValue = getter.apply(actual);
+
+ if (expectedValue == null && actualValue == null) {
+ return true;
+ } else if (expectedValue == null || actualValue == null) {
+ this.addTemplatedDescription(attribute, expectedValue);
+ return false;
+ } else {
+ final Matcher> matcher =
+ matcherFactory.apply(expectedValue);
+
+ if (matcher.matches(actualValue)) {
+ return true;
+ } else {
+ this.addMatcherDescription(matcher);
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Compares the expected and the actual value of an attribute. If the
+ * comparison fails, the description of the error will be updated.
+ *
+ * @param attribute the name of the attribute compared.
+ * @param getter the getter function of the attribute.
+ * @param actual the actual entity being compared to the expected entity.
+ * @param type of the value returned by the getter.
+ * @return {@code true} if the value of the expected and actual attributes
+ * are equals and {@code false} otherwise. If the result is {@code false},
+ * the current description will be updated.
+ */
+ protected boolean checkAttribute(
+ final String attribute, final Function getter, final T actual
+ ) {
+ final R expectedValue = getter.apply(this.expected);
+ final R actualValue = getter.apply(actual);
+
+ if (expectedValue == null && actualValue == null) {
+ return true;
+ } else if (expectedValue == null || !expectedValue.equals(actualValue)) {
+ this.addTemplatedDescription(attribute, expectedValue);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Compares the expected and the actual value of an array attribute. If the
+ * comparison fails, the description of the error will be updated.
+ *
+ * @param attribute the name of the attribute compared.
+ * @param getter the getter function of the attribute.
+ * @param actual the actual entity being compared to the expected entity.
+ * @param type of the value returned by the getter.
+ * @return {@code true} if the value of the expected and actual attributes
+ * are equals and {@code false} otherwise. If the result is {@code false},
+ * the current description will be updated.
+ */
+ protected boolean checkArrayAttribute(
+ final String attribute, final Function getter, final T actual
+ ) {
+ final R[] expectedValue = getter.apply(this.expected);
+ final R[] actualValue = getter.apply(actual);
+
+ if (expectedValue == null && actualValue == null) {
+ return true;
+ } else if (expectedValue == null || actualValue == null) {
+ this.addTemplatedDescription(attribute, expectedValue == null ? "null" : Arrays.toString(expectedValue));
+ return false;
+ } else if (!Arrays.equals(expectedValue, actualValue)) {
+ this.addTemplatedDescription(attribute, Arrays.toString(expectedValue));
+ return false;
+ } else
+ return true;
+ }
+
+ /**
+ * Compares the expected and the actual value of an int array attribute. If
+ * the comparison fails, the description of the error will be updated.
+ *
+ * @param attribute the name of the attribute compared.
+ * @param getter the getter function of the attribute.
+ * @param actual the actual entity being compared to the expected entity.
+ * @param type of the value returned by the getter.
+ * @return {@code true} if the value of the expected and actual attributes
+ * are equals and {@code false} otherwise. If the result is {@code false},
+ * the current description will be updated.
+ */
+ protected boolean checkIntArrayAttribute(
+ final String attribute, final Function getter, final T actual
+ ) {
+ final int[] expectedValue = getter.apply(this.expected);
+ final int[] actualValue = getter.apply(actual);
+
+ if (expectedValue == null && actualValue == null) {
+ return true;
+ } else if (expectedValue == null || actualValue == null) {
+ this.addTemplatedDescription(attribute, expectedValue == null ? "null" : Arrays.toString(expectedValue));
+ return false;
+ } else if (!Arrays.equals(expectedValue, actualValue)) {
+ this.addTemplatedDescription(attribute, Arrays.toString(expectedValue));
+ return false;
+ } else
+ return true;
+ }
+
+ /**
+ * Utility method that generates a {@link Matcher} that compares several
+ * entities.
+ *
+ * @param converter a function to create a matcher for an entity.
+ * @param entities the entities to be used as the expected values.
+ * @param type of the entity.
+ * @return a new {@link Matcher} that compares several entities.
+ */
+ @SafeVarargs
+ protected static Matcher> containsEntityInAnyOrder(
+ final Function> converter, final T ... entities
+ ) {
+ final Collection> entitiesMatchers = stream(entities)
+ .map(converter)
+ .collect(toList());
+
+ return containsInAnyOrder(entitiesMatchers);
+ }
+
+ /**
+ * Utility method that generates a {@link Matcher} that compares several
+ * entities.
+ *
+ * @param converter a function to create a matcher for an entity.
+ * @param entities the entities to be used as the expected values.
+ * @param type of the entity.
+ * @return a new {@link Matcher} that compares several entities.
+ */
+ protected static Matcher> containsEntityInAnyOrder(
+ final Function> converter, final Iterable entities
+ ) {
+ final Collection> entitiesMatchers =
+ StreamSupport.stream(entities.spliterator(), false)
+ .map(converter)
+ .collect(toList());
+
+ return containsInAnyOrder(entitiesMatchers);
+ }
+
+ /**
+ * Utility method that generates a {@link Matcher} that compares several
+ * entities in the same received order.
+ *
+ * @param converter A function to create a matcher for an entity.
+ * @param entities The entities to be used as the expected values, in the
+ * order to be compared.
+ * @param The type of the entity.
+ *
+ * @return A new {@link Matcher} that compares several entities in the same
+ * received order.
+ */
+ @SafeVarargs
+ protected static Matcher> containsEntityInOrder(
+ final Function> converter, final T ... entities
+ ) {
+ return contains(stream(entities).map(converter).collect(toList()));
+ }
+
+ /**
+ * Utility method that generates a {@link Matcher} that compares several
+ * entities in the same received order.
+ *
+ * @param converter A function to create a matcher for an entity.
+ * @param entities The entities to be used as the expected values, in the
+ * order to be compared.
+ * @param The type of the entity.
+ *
+ * @return A new {@link Matcher} that compares several entities in the same
+ * received order.
+ */
+ protected static Matcher> containsEntityInOrder(
+ final Function> converter, final Iterable entities
+ ) {
+ final List> matchersList =
+ StreamSupport.stream(entities.spliterator(), false)
+ .map(converter)
+ .collect(toList());
+
+ return contains(matchersList);
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToPerson.java b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToPerson.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c06e5f0ff2b8d7e69d96da15b7af78cfd3c62e3
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToPerson.java
@@ -0,0 +1,56 @@
+package es.uvigo.esei.daa.matchers;
+
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+import es.uvigo.esei.daa.entities.Person;
+
+public class IsEqualToPerson extends IsEqualToEntity {
+ public IsEqualToPerson(Person entity) {
+ super(entity);
+ }
+
+ @Override
+ protected boolean matchesSafely(Person actual) {
+ this.clearDescribeTo();
+
+ if (actual == null) {
+ this.addTemplatedDescription("actual", expected.toString());
+ return false;
+ } else {
+ return checkAttribute("id", Person::getId, actual)
+ && checkAttribute("name", Person::getName, actual)
+ && checkAttribute("surname", Person::getSurname, actual);
+ }
+ }
+
+ /**
+ * Factory method that creates a new {@link IsEqualToEntity} matcher with
+ * the provided {@link Person} as the expected value.
+ *
+ * @param person the expected person.
+ * @return a new {@link IsEqualToEntity} matcher with the provided
+ * {@link Person} as the expected value.
+ */
+ @Factory
+ public static IsEqualToPerson equalsToPerson(Person person) {
+ return new IsEqualToPerson(person);
+ }
+
+ /**
+ * Factory method that returns a new {@link Matcher} that includes several
+ * {@link IsEqualToPerson} matchers, each one using an {@link Person} of the
+ * provided ones as the expected value.
+ *
+ * @param persons the persons to be used as the expected values.
+ * @return a new {@link Matcher} that includes several
+ * {@link IsEqualToPerson} matchers, each one using an {@link Person} of the
+ * provided ones as the expected value.
+ * @see IsEqualToEntity#containsEntityInAnyOrder(java.util.function.Function, Object...)
+ */
+ @Factory
+ public static Matcher> containsPeopleInAnyOrder(Person ... persons) {
+ return containsEntityInAnyOrder(IsEqualToPerson::equalsToPerson, persons);
+ }
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToPet.java b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToPet.java
new file mode 100644
index 0000000000000000000000000000000000000000..81e55ed8d414e808b56f151f2044ab71ce73f627
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToPet.java
@@ -0,0 +1,63 @@
+package es.uvigo.esei.daa.matchers;
+
+import static org.junit.Assert.*;
+
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import es.uvigo.esei.daa.entities.Person;
+import es.uvigo.esei.daa.entities.Pet;
+
+public class IsEqualToPet extends IsEqualToEntity{
+
+ public IsEqualToPet(Pet entity) {
+ super(entity);
+ }
+
+ @Override
+ protected boolean matchesSafely(Pet actual) {
+ this.clearDescribeTo();
+
+ if (actual == null) {
+ this.addTemplatedDescription("actual", expected.toString());
+ return false;
+ } else {
+ return checkAttribute("id", Pet::getId, actual)
+ && checkAttribute("name", Pet::getName, actual)
+ && checkAttribute("owner", Pet::getOwner, actual)
+ && checkAttribute("typeofanimal", Pet::getTypeOfAnimal, actual);
+
+ }
+ }
+
+ /**
+ * Factory method that creates a new {@link IsEqualToEntity} matcher with
+ * the provided {@link Person} as the expected value.
+ *
+ * @param person the expected person.
+ * @return a new {@link IsEqualToEntity} matcher with the provided
+ * {@link Person} as the expected value.
+ */
+ @Factory
+ public static IsEqualToPet equalsToPet(Pet pet) {
+ return new IsEqualToPet(pet);
+ }
+
+ /**
+ * Factory method that returns a new {@link Matcher} that includes several
+ * {@link IsEqualToPerson} matchers, each one using an {@link Person} of the
+ * provided ones as the expected value.
+ *
+ * @param persons the persons to be used as the expected values.
+ * @return a new {@link Matcher} that includes several
+ * {@link IsEqualToPerson} matchers, each one using an {@link Person} of the
+ * provided ones as the expected value.
+ * @see IsEqualToEntity#containsEntityInAnyOrder(java.util.function.Function, Object...)
+ */
+ @Factory
+ public static Matcher> containsPetsInAnyOrder(Pet ... pets) {
+ return containsEntityInAnyOrder(IsEqualToPet::equalsToPet, pets);
+ }
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToUser.java b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToUser.java
new file mode 100644
index 0000000000000000000000000000000000000000..689674ea1e7e97580ce16377bc3109a668e4768c
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/matchers/IsEqualToUser.java
@@ -0,0 +1,56 @@
+package es.uvigo.esei.daa.matchers;
+
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+import es.uvigo.esei.daa.entities.Person;
+import es.uvigo.esei.daa.entities.User;
+
+public class IsEqualToUser extends IsEqualToEntity {
+ public IsEqualToUser(User entity) {
+ super(entity);
+ }
+
+ @Override
+ protected boolean matchesSafely(User actual) {
+ this.clearDescribeTo();
+
+ if (actual == null) {
+ this.addTemplatedDescription("actual", expected.toString());
+ return false;
+ } else {
+ return checkAttribute("login", User::getLogin, actual)
+ && checkAttribute("password", User::getPassword, actual);
+ }
+ }
+
+ /**
+ * Factory method that creates a new {@link IsEqualToEntity} matcher with
+ * the provided {@link Person} as the expected value.
+ *
+ * @param user the expected person.
+ * @return a new {@link IsEqualToEntity} matcher with the provided
+ * {@link Person} as the expected value.
+ */
+ @Factory
+ public static IsEqualToUser equalsToUser(User user) {
+ return new IsEqualToUser(user);
+ }
+
+ /**
+ * Factory method that returns a new {@link Matcher} that includes several
+ * {@link IsEqualToUser} matchers, each one using an {@link Person} of the
+ * provided ones as the expected value.
+ *
+ * @param users the persons to be used as the expected values.
+ * @return a new {@link Matcher} that includes several
+ * {@link IsEqualToUser} matchers, each one using an {@link Person} of the
+ * provided ones as the expected value.
+ * @see IsEqualToEntity#containsEntityInAnyOrder(java.util.function.Function, Object...)
+ */
+ @Factory
+ public static Matcher> containsPeopleInAnyOrder(User ... users) {
+ return containsEntityInAnyOrder(IsEqualToUser::equalsToUser, users);
+ }
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/rest/PeopleResourceTest.java b/src/test/java/es/uvigo/esei/daa/rest/PeopleResourceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4cbc8be87daee45673830bfebfed6171a9135df9
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/rest/PeopleResourceTest.java
@@ -0,0 +1,291 @@
+package es.uvigo.esei.daa.rest;
+
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
+import static es.uvigo.esei.daa.dataset.UsersDataset.adminLogin;
+import static es.uvigo.esei.daa.dataset.UsersDataset.normalLogin;
+import static es.uvigo.esei.daa.dataset.UsersDataset.userToken;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasBadRequestStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
+import static javax.ws.rs.client.Entity.entity;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.sql.DataSource;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import com.github.springtestdbunit.DbUnitTestExecutionListener;
+import com.github.springtestdbunit.annotation.DatabaseSetup;
+import com.github.springtestdbunit.annotation.ExpectedDatabase;
+
+import es.uvigo.esei.daa.DAAExampleTestApplication;
+import es.uvigo.esei.daa.entities.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 {
+ @Override
+ protected Application configure() {
+ return new DAAExampleTestApplication();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ super.configureClient(config);
+
+ // Enables JSON transformation in client
+ config.register(JacksonJsonProvider.class);
+ config.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
+ }
+
+ @Test
+ public void testList() throws IOException {
+ final Response response = target("people").request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final List people = response.readEntity(new GenericType>(){});
+
+ assertThat(people, containsPeopleInAnyOrder(people()));
+ }
+
+ @Test
+ public void testListUnauthorized() throws IOException {
+ final Response response = target("people").request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .get();
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testGet() throws IOException {
+ final Response response = target("people/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final Person person = response.readEntity(Person.class);
+
+ assertThat(person, is(equalsToPerson(existentPerson())));
+ }
+
+ @Test
+ public void testGetUnauthorized() throws IOException {
+ final Response response = target("people/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .get();
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testGetInvalidId() throws IOException {
+ final Response response = target("people/" + nonExistentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .get();
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-add.xml")
+ public void testAdd() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("surname", newSurname());
+
+ final Response response = target("people").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+ assertThat(response, hasOkStatus());
+
+ final Person person = response.readEntity(Person.class);
+
+ assertThat(person, is(equalsToPerson(newPerson())));
+ }
+
+ @Test
+ public void testAddUnauthorized() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("surname", newSurname());
+
+ final Response response = target("people").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testAddMissingName() throws IOException {
+ final Form form = new Form();
+ form.param("surname", newSurname());
+
+ final Response response = target("people").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testAddMissingSurname() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+
+ final Response response = target("people").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-modify.xml")
+ public void testModify() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("surname", newSurname());
+
+ final Response response = target("people/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+ assertThat(response, hasOkStatus());
+
+ final Person modifiedPerson = response.readEntity(Person.class);
+
+ final Person person = existentPerson();
+ person.setName(newName());
+ person.setSurname(newSurname());
+
+ assertThat(modifiedPerson, is(equalsToPerson(person)));
+ }
+
+ @Test
+ public void testModifyUnauthorized() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("surname", newSurname());
+
+ final Response response = target("people/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testModifyName() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+
+ final Response response = target("people/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifySurname() throws IOException {
+ final Form form = new Form();
+ form.param("surname", newSurname());
+
+ final Response response = target("people/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifyInvalidId() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("surname", newSurname());
+
+ final Response response = target("people/" + nonExistentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-delete.xml")
+ public void testDelete() throws IOException {
+ final Response response = target("people/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .delete();
+
+ assertThat(response, hasOkStatus());
+
+ final Integer deletedId = response.readEntity(Integer.class);
+
+ assertThat(deletedId, is(equalTo(existentId())));
+ }
+
+ @Test
+ public void testDeleteUnauthorized() throws IOException {
+ final Response response = target("people/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .delete();
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testDeleteInvalidId() throws IOException {
+ final Response response = target("people/" + nonExistentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .delete();
+
+ assertThat(response, hasBadRequestStatus());
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/rest/PeopleResourceUnitTest.java b/src/test/java/es/uvigo/esei/daa/rest/PeopleResourceUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cb75654a8eca50305099a1cf3959f052e8f1e38
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/rest/PeopleResourceUnitTest.java
@@ -0,0 +1,242 @@
+package es.uvigo.esei.daa.rest;
+
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasBadRequestStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasInternalServerErrorStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
+import static java.util.Arrays.asList;
+import static org.easymock.EasyMock.anyInt;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import es.uvigo.esei.daa.dao.DAOException;
+import es.uvigo.esei.daa.dao.PeopleDAO;
+import es.uvigo.esei.daa.entities.Person;
+
+public class PeopleResourceUnitTest {
+ private PeopleDAO daoMock;
+ private PeopleResource resource;
+
+ @Before
+ public void setUp() throws Exception {
+ daoMock = createMock(PeopleDAO.class);
+ resource = new PeopleResource(daoMock);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ try {
+ verify(daoMock);
+ } finally {
+ daoMock = null;
+ resource = null;
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testList() throws Exception {
+ final List people = asList(people());
+
+ expect(daoMock.list()).andReturn(people);
+
+ replay(daoMock);
+
+ final Response response = resource.list();
+
+ assertThat(response, hasOkStatus());
+ assertThat((List) response.getEntity(), containsPeopleInAnyOrder(people()));
+ }
+
+ @Test
+ public void testListDAOException() throws Exception {
+ expect(daoMock.list()).andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.list();
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ final Person person = existentPerson();
+
+ expect(daoMock.get(person.getId())).andReturn(person);
+
+ replay(daoMock);
+
+ final Response response = resource.get(person.getId());
+
+ assertThat(response, hasOkStatus());
+ assertThat((Person) response.getEntity(), is(equalsToPerson(person)));
+ }
+
+ @Test
+ public void testGetDAOException() throws Exception {
+ expect(daoMock.get(anyInt())).andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.get(existentId());
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testGetIllegalArgumentException() throws Exception {
+ expect(daoMock.get(anyInt())).andThrow(new IllegalArgumentException());
+
+ replay(daoMock);
+
+ final Response response = resource.get(existentId());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ daoMock.delete(anyInt());
+
+ replay(daoMock);
+
+ final Response response = resource.delete(1);
+
+ assertThat(response, hasOkStatus());
+ }
+
+ @Test
+ public void testDeleteDAOException() throws Exception {
+ daoMock.delete(anyInt());
+ expectLastCall().andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.delete(1);
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testDeleteIllegalArgumentException() throws Exception {
+ daoMock.delete(anyInt());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(daoMock);
+
+ final Response response = resource.delete(1);
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModify() throws Exception {
+ final Person person = existentPerson();
+ person.setName(newName());
+ person.setSurname(newSurname());
+
+ daoMock.modify(person);
+
+ replay(daoMock);
+
+ final Response response = resource.modify(
+ person.getId(), person.getName(), person.getSurname());
+
+ assertThat(response, hasOkStatus());
+ assertEquals(person, response.getEntity());
+ }
+
+ @Test
+ public void testModifyDAOException() throws Exception {
+ daoMock.modify(anyObject());
+ expectLastCall().andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.modify(existentId(), newName(), newSurname());
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testModifyIllegalArgumentException() throws Exception {
+ daoMock.modify(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+
+ replay(daoMock);
+
+ final Response response = resource.modify(existentId(), newName(), newSurname());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifyNullPointerException() throws Exception {
+ daoMock.modify(anyObject());
+ expectLastCall().andThrow(new NullPointerException());
+
+ replay(daoMock);
+
+ final Response response = resource.modify(existentId(), newName(), newSurname());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testAdd() throws Exception {
+ expect(daoMock.add(newName(), newSurname()))
+ .andReturn(newPerson());
+ replay(daoMock);
+
+
+ final Response response = resource.add(newName(), newSurname());
+
+ assertThat(response, hasOkStatus());
+ assertThat((Person) response.getEntity(), is(equalsToPerson(newPerson())));
+ }
+
+ @Test
+ public void testAddDAOException() throws Exception {
+ expect(daoMock.add(anyString(), anyString()))
+ .andThrow(new DAOException());
+ replay(daoMock);
+
+ final Response response = resource.add(newName(), newSurname());
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testAddIllegalArgumentException() throws Exception {
+ expect(daoMock.add(anyString(), anyString()))
+ .andThrow(new IllegalArgumentException());
+ replay(daoMock);
+
+ final Response response = resource.add(newName(), newSurname());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/rest/PetsResourceTest.java b/src/test/java/es/uvigo/esei/daa/rest/PetsResourceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2d3b3d3e6f0d38c2977177687c2ee67d4103f44
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/rest/PetsResourceTest.java
@@ -0,0 +1,327 @@
+package es.uvigo.esei.daa.rest;
+
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newName;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newOwner;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newTypeOfAnimal;
+import static es.uvigo.esei.daa.dataset.PetsDataset.nonExistentId;
+import static es.uvigo.esei.daa.dataset.PetsDataset.pets;
+import static es.uvigo.esei.daa.dataset.UsersDataset.adminLogin;
+import static es.uvigo.esei.daa.dataset.UsersDataset.normalLogin;
+import static es.uvigo.esei.daa.dataset.UsersDataset.userToken;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasBadRequestStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.containsPetsInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.equalsToPet;
+import static javax.ws.rs.client.Entity.entity;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.sql.DataSource;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import com.github.springtestdbunit.DbUnitTestExecutionListener;
+import com.github.springtestdbunit.annotation.DatabaseSetup;
+import com.github.springtestdbunit.annotation.ExpectedDatabase;
+
+import es.uvigo.esei.daa.DAAExampleTestApplication;
+import es.uvigo.esei.daa.entities.Pet;
+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 PetsResourceTest extends JerseyTest {
+ @Override
+ protected Application configure() {
+ return new DAAExampleTestApplication();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ super.configureClient(config);
+
+ // Enables JSON transformation in client
+ config.register(JacksonJsonProvider.class);
+ config.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
+ }
+
+ @Test
+ public void testList() throws IOException {
+ final Response response = target("pets/").request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final List pets = response.readEntity(new GenericType>(){});
+
+ assertThat(pets, containsPetsInAnyOrder(pets()));
+ }
+
+ @Test
+ public void testListUnauthorized() throws IOException {
+ final Response response = target("pets/").request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .get();
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testGet() throws IOException {
+ final Response response = target("pets/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final Pet pet = response.readEntity(Pet.class);
+
+ assertThat(pet, is(equalsToPet(existentPet())));
+ }
+
+ @Test
+ public void testGetUnauthorized() throws IOException {
+ final Response response = target("pets/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .get();
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testGetInvalidId() throws IOException {
+ final Response response = target("pets/" + nonExistentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .get();
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-add-pets.xml")
+ public void testAdd() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("owner", Integer.toString(newOwner()));
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+ assertThat(response, hasOkStatus());
+
+ final Pet pet = response.readEntity(Pet.class);
+
+ assertThat(pet, is(equalsToPet(newPet())));
+ }
+
+ @Test
+ public void testAddUnauthorized() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("owner", Integer.toString(newOwner()));
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testAddMissingName() throws IOException {
+ final Form form = new Form();
+ form.param("owner", Integer.toString(newOwner()));
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testAddMissingOwner() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testAddMissingTypeOfAnimal() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("owner", Integer.toString(newOwner()));
+
+ final Response response = target("pets/").request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .post(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-modify-pets.xml")
+ public void testModify() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("owner", Integer.toString(newOwner()));
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+ assertThat(response, hasOkStatus());
+
+ final Pet modifiedPet = response.readEntity(Pet.class);
+
+ final Pet pet = existentPet();
+ pet.setName(newName());
+ pet.setOwner(newOwner());
+ pet.setTypeOfAnimal(newTypeOfAnimal());
+
+ assertThat(modifiedPet, is(equalsToPet(pet)));
+ }
+
+ @Test
+ public void testModifyUnauthorized() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("owner", Integer.toString(newOwner()));
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testModifyName() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+
+ final Response response = target("pets/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifyOwner() throws IOException {
+ final Form form = new Form();
+ form.param("owner", Integer.toString(newOwner()));
+
+ final Response response = target("pets/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifyTypeOfAnimal() throws IOException {
+ final Form form = new Form();
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/" + existentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifyInvalidId() throws IOException {
+ final Form form = new Form();
+ form.param("name", newName());
+ form.param("owner", Integer.toString(newOwner()));
+ form.param("typeOfAnimal", newTypeOfAnimal());
+
+ final Response response = target("pets/" + nonExistentId()).request(MediaType.APPLICATION_JSON_TYPE)
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .put(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-delete-pets.xml")
+ public void testDelete() throws IOException {
+ final Response response = target("pets/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .delete();
+
+ assertThat(response, hasOkStatus());
+
+ final Integer deletedId = response.readEntity(Integer.class);
+
+ assertThat(deletedId, is(equalTo(existentId())));
+ }
+
+ @Test
+ public void testDeleteUnauthorized() throws IOException {
+ final Response response = target("pets/" + existentId()).request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .delete();
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testDeleteInvalidId() throws IOException {
+ final Response response = target("pets/" + nonExistentId()).request()
+ .header("Authorization", "Basic " + userToken(adminLogin()))
+ .delete();
+
+ assertThat(response, hasBadRequestStatus());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/es/uvigo/esei/daa/rest/PetsResourceUnitTest.java b/src/test/java/es/uvigo/esei/daa/rest/PetsResourceUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4997c24be9de9f8eba15e0303ee59f1061031bd
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/rest/PetsResourceUnitTest.java
@@ -0,0 +1,246 @@
+package es.uvigo.esei.daa.rest;
+
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PetsDataset.existentPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newName;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newPet;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newOwner;
+import static es.uvigo.esei.daa.dataset.PetsDataset.newTypeOfAnimal;
+import static es.uvigo.esei.daa.dataset.PetsDataset.pets;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasBadRequestStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasInternalServerErrorStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.containsPetsInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPet.equalsToPet;
+import static java.util.Arrays.asList;
+import static org.easymock.EasyMock.anyInt;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import es.uvigo.esei.daa.dao.DAOException;
+import es.uvigo.esei.daa.dao.PetsDAO;
+import es.uvigo.esei.daa.entities.Pet;
+
+public class PetsResourceUnitTest {
+
+ private PetsDAO daoMock;
+ private PetsResource resource;
+
+ @Before
+ public void setUp() throws Exception {
+ daoMock = createMock(PetsDAO.class);
+ resource = new PetsResource(daoMock);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ try {
+ verify(daoMock);
+ } finally {
+ daoMock = null;
+ resource = null;
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testList() throws Exception {
+ final List pets = asList(pets());
+
+ expect(daoMock.list()).andReturn(pets);
+
+ replay(daoMock);
+
+ final Response response = resource.list();
+
+ assertThat(response, hasOkStatus());
+ assertThat((List) response.getEntity(), containsPetsInAnyOrder(pets()));
+ }
+
+ @Test
+ public void testListDAOException() throws Exception {
+ expect(daoMock.list()).andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.list();
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ final Pet pet = existentPet();
+
+ expect(daoMock.get(pet.getId())).andReturn(pet);
+
+ replay(daoMock);
+
+ final Response response = resource.get(pet.getId());
+
+ assertThat(response, hasOkStatus());
+ assertThat((Pet) response.getEntity(), is(equalsToPet(pet)));
+ }
+
+ @Test
+ public void testGetDAOException() throws Exception {
+ expect(daoMock.get(anyInt())).andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.get(existentId());
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testGetIllegalArgumentException() throws Exception {
+ expect(daoMock.get(anyInt())).andThrow(new IllegalArgumentException());
+
+ replay(daoMock);
+
+ final Response response = resource.get(existentId());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ daoMock.delete(anyInt());
+
+ replay(daoMock);
+
+ final Response response = resource.delete(1);
+
+ assertThat(response, hasOkStatus());
+ }
+
+ @Test
+ public void testDeleteDAOException() throws Exception {
+ daoMock.delete(anyInt());
+ expectLastCall().andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.delete(1);
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testDeleteIllegalArgumentException() throws Exception {
+ daoMock.delete(anyInt());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(daoMock);
+
+ final Response response = resource.delete(1);
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModify() throws Exception {
+ final Pet pet = existentPet();
+ pet.setName(newName());
+ pet.setOwner(newOwner());
+ pet.setTypeOfAnimal(newTypeOfAnimal());
+
+
+ daoMock.modify(pet);
+
+ replay(daoMock);
+
+ final Response response = resource.modify(
+ pet.getId(), pet.getName(), pet.getOwner(),pet.getTypeOfAnimal());
+
+ assertThat(response, hasOkStatus());
+ assertEquals(pet, response.getEntity());
+ }
+
+ @Test
+ public void testModifyDAOException() throws Exception {
+ daoMock.modify(anyObject());
+ expectLastCall().andThrow(new DAOException());
+
+ replay(daoMock);
+
+ final Response response = resource.modify(existentId(), newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testModifyIllegalArgumentException() throws Exception {
+ daoMock.modify(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+
+ replay(daoMock);
+
+ final Response response = resource.modify(existentId(), newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testModifyNullPointerException() throws Exception {
+ daoMock.modify(anyObject());
+ expectLastCall().andThrow(new NullPointerException());
+
+ replay(daoMock);
+
+ final Response response = resource.modify(existentId(), newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+ @Test
+ public void testAdd() throws Exception {
+ expect(daoMock.add(newName(), newOwner(), newTypeOfAnimal()))
+ .andReturn(newPet());
+ replay(daoMock);
+
+
+ final Response response = resource.add(newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(response, hasOkStatus());
+ assertThat((Pet) response.getEntity(), is(equalsToPet(newPet())));
+ }
+
+ @Test
+ public void testAddDAOException() throws Exception {
+ expect(daoMock.add(anyString(), anyInt(), anyString()))
+ .andThrow(new DAOException());
+ replay(daoMock);
+
+ final Response response = resource.add(newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(response, hasInternalServerErrorStatus());
+ }
+
+ @Test
+ public void testAddIllegalArgumentException() throws Exception {
+ expect(daoMock.add(anyString(), anyInt(),anyString()))
+ .andThrow(new IllegalArgumentException());
+ replay(daoMock);
+
+ final Response response = resource.add(newName(), newOwner(), newTypeOfAnimal());
+
+ assertThat(response, hasBadRequestStatus());
+ }
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java b/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c74ad1d3e3531d283512c301ec3d54ac72cb28a
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java
@@ -0,0 +1,138 @@
+package es.uvigo.esei.daa.rest;
+
+import static es.uvigo.esei.daa.dataset.UsersDataset.adminLogin;
+import static es.uvigo.esei.daa.dataset.UsersDataset.normalLogin;
+import static es.uvigo.esei.daa.dataset.UsersDataset.user;
+import static es.uvigo.esei.daa.dataset.UsersDataset.userToken;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
+import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized;
+import static es.uvigo.esei.daa.matchers.IsEqualToUser.equalsToUser;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+
+import javax.sql.DataSource;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import com.github.springtestdbunit.DbUnitTestExecutionListener;
+import com.github.springtestdbunit.annotation.DatabaseSetup;
+import com.github.springtestdbunit.annotation.ExpectedDatabase;
+
+import es.uvigo.esei.daa.DAAExampleTestApplication;
+import es.uvigo.esei.daa.entities.User;
+import es.uvigo.esei.daa.listeners.ApplicationContextBinding;
+import es.uvigo.esei.daa.listeners.ApplicationContextJndiBindingTestExecutionListener;
+import es.uvigo.esei.daa.listeners.DbManagement;
+import es.uvigo.esei.daa.listeners.DbManagementTestExecutionListener;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:contexts/mem-context.xml")
+@TestExecutionListeners({
+ DbUnitTestExecutionListener.class,
+ DbManagementTestExecutionListener.class,
+ ApplicationContextJndiBindingTestExecutionListener.class
+})
+@ApplicationContextBinding(
+ jndiUrl = "java:/comp/env/jdbc/daaexample",
+ type = DataSource.class
+)
+@DbManagement(
+ create = "classpath:db/hsqldb.sql",
+ drop = "classpath:db/hsqldb-drop.sql"
+)
+@DatabaseSetup("/datasets/dataset.xml")
+@ExpectedDatabase("/datasets/dataset.xml")
+public class UsersResourceTest extends JerseyTest {
+ @Override
+ protected Application configure() {
+ return new DAAExampleTestApplication();
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ super.configureClient(config);
+
+ // Enables JSON transformation in client
+ config.register(JacksonJsonProvider.class);
+ config.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
+ }
+
+ @Test
+ public void testGetAdminOwnUser() throws IOException {
+ final String admin = adminLogin();
+
+ final Response response = target("users/" + admin).request()
+ .header("Authorization", "Basic " + userToken(admin))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final User user = response.readEntity(User.class);
+
+ assertThat(user, is(equalsToUser(user(admin))));
+ }
+
+ @Test
+ public void testGetAdminOtherUser() throws IOException {
+ final String admin = adminLogin();
+ final String otherUser = normalLogin();
+
+ final Response response = target("users/" + otherUser).request()
+ .header("Authorization", "Basic " + userToken(admin))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final User user = response.readEntity(User.class);
+
+ assertThat(user, is(equalsToUser(user(otherUser))));
+ }
+
+ @Test
+ public void testGetNormalOwnUser() throws IOException {
+ final String login = normalLogin();
+
+ final Response response = target("users/" + login).request()
+ .header("Authorization", "Basic " + userToken(login))
+ .get();
+ assertThat(response, hasOkStatus());
+
+ final User user = response.readEntity(User.class);
+
+ assertThat(user, is(equalsToUser(user(login))));
+ }
+
+ @Test
+ public void testGetNoCredentials() throws IOException {
+ final Response response = target("users/" + normalLogin()).request().get();
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testGetBadCredentials() throws IOException {
+ final Response response = target("users/" + adminLogin()).request()
+ .header("Authorization", "Basic YmFkOmNyZWRlbnRpYWxz")
+ .get();
+
+ assertThat(response, hasUnauthorized());
+ }
+
+ @Test
+ public void testGetIllegalAccess() throws IOException {
+ final Response response = target("users/" + adminLogin()).request()
+ .header("Authorization", "Basic " + userToken(normalLogin()))
+ .get();
+
+ assertThat(response, hasUnauthorized());
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/suites/AcceptanceTestSuite.java b/src/test/java/es/uvigo/esei/daa/suites/AcceptanceTestSuite.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e7fcdae98a373f1c05365a54f4c40f3166f0ba4
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/suites/AcceptanceTestSuite.java
@@ -0,0 +1,15 @@
+package es.uvigo.esei.daa.suites;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+import es.uvigo.esei.daa.web.PeopleWebTest;
+
+@SuiteClasses({
+ PeopleWebTest.class
+})
+@RunWith(Suite.class)
+public class AcceptanceTestSuite {
+
+}
diff --git a/src/test/java/es/uvigo/esei/daa/suites/IntegrationTestSuite.java b/src/test/java/es/uvigo/esei/daa/suites/IntegrationTestSuite.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9b10f2976413e653147ad7625bc93a036383705
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/suites/IntegrationTestSuite.java
@@ -0,0 +1,23 @@
+package es.uvigo.esei.daa.suites;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+import es.uvigo.esei.daa.dao.PeopleDAOTest;
+import es.uvigo.esei.daa.rest.PeopleResourceTest;
+import es.uvigo.esei.daa.rest.UsersResourceTest;
+import es.uvigo.esei.daa.rest.PetsResourceTest;
+import es.uvigo.esei.daa.dao.PetsDAOTest;
+
+@SuiteClasses({
+ PeopleDAOTest.class,
+ PeopleResourceTest.class,
+ UsersResourceTest.class,
+ PetsDAOTest.class,
+ PetsResourceTest.class,
+
+})
+@RunWith(Suite.class)
+public class IntegrationTestSuite {
+}
diff --git a/src/test/java/es/uvigo/esei/daa/suites/UnitTestSuite.java b/src/test/java/es/uvigo/esei/daa/suites/UnitTestSuite.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f3e9ed223959bc4564a893507b3d779ddf09513
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/suites/UnitTestSuite.java
@@ -0,0 +1,26 @@
+package es.uvigo.esei.daa.suites;
+
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+import es.uvigo.esei.daa.dao.PeopleDAOUnitTest;
+import es.uvigo.esei.daa.entities.PersonUnitTest;
+import es.uvigo.esei.daa.rest.PeopleResourceUnitTest;
+
+import es.uvigo.esei.daa.dao.PetsDAOUnitTest;
+import es.uvigo.esei.daa.entities.PetUnitTest;
+import es.uvigo.esei.daa.rest.PetsResourceUnitTest;
+
+@SuiteClasses({
+ PersonUnitTest.class,
+ PeopleDAOUnitTest.class,
+ PeopleResourceUnitTest.class,
+ PetUnitTest.class,
+ PetsDAOUnitTest.class,
+ PetsResourceUnitTest.class
+})
+@RunWith(Suite.class)
+public class UnitTestSuite {
+}
diff --git a/src/test/java/es/uvigo/esei/daa/util/ContextUtils.java b/src/test/java/es/uvigo/esei/daa/util/ContextUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..331044f0f1f201bf6d93b2805d08ac4c85031b4c
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/util/ContextUtils.java
@@ -0,0 +1,24 @@
+package es.uvigo.esei.daa.util;
+
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import org.springframework.mock.jndi.SimpleNamingContextBuilder;
+
+public final class ContextUtils {
+ private final static SimpleNamingContextBuilder CONTEXT_BUILDER =
+ new SimpleNamingContextBuilder();
+
+ private ContextUtils() {}
+
+ public static void createFakeContext(DataSource datasource)
+ throws IllegalStateException, NamingException {
+ CONTEXT_BUILDER.bind("java:/comp/env/jdbc/daaexample", datasource);
+ CONTEXT_BUILDER.activate();
+ }
+
+ public static void clearContextBuilder() {
+ CONTEXT_BUILDER.clear();
+ CONTEXT_BUILDER.deactivate();
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/util/DatabaseQueryUnitTest.java b/src/test/java/es/uvigo/esei/daa/util/DatabaseQueryUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e541cb7ce2edb3b91ed6c40164b296ef10a3f34c
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/util/DatabaseQueryUnitTest.java
@@ -0,0 +1,106 @@
+package es.uvigo.esei.daa.util;
+
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+
+import com.mysql.jdbc.PreparedStatement;
+
+/**
+ * Super-class for unit tests in the DAO layer.
+ *
+ *
The default {@link DatabaseQueryUnitTest#setUp()} method in this class
+ * create mocks for the datasource, connection, statement, and result variables
+ * that can be used by the DAO object under test.
+ *
+ * @author Miguel Reboiro Jato
+ */
+public abstract class DatabaseQueryUnitTest {
+ protected DataSource datasource;
+ protected Connection connection;
+ protected PreparedStatement statement;
+ protected ResultSet result;
+
+ protected boolean verify;
+
+ /**
+ * Configures the mocks and enables the verification.
+ *
+ * @throws Exception if an error happens while configuring the mocks.
+ */
+ @Before
+ public void setUp() throws Exception {
+ datasource = createMock(DataSource.class);
+ connection = createMock(Connection.class);
+ statement = createNiceMock(PreparedStatement.class);
+ result = createMock(ResultSet.class);
+
+ expect(datasource.getConnection())
+ .andReturn(connection);
+ expect(connection.prepareStatement(anyString()))
+ .andReturn(statement)
+ .anyTimes(); // statement is optional
+ expect(statement.executeQuery())
+ .andReturn(result)
+ .anyTimes(); // executeQuery is optional
+ statement.close();
+ connection.close();
+
+ verify = true;
+ }
+
+ /**
+ * Removes the default behavior of the mock instances and disables the mock
+ * verification.
+ */
+ protected void resetAll() {
+ reset(result, statement, connection, datasource);
+ verify = false;
+ }
+
+ /**
+ * Replays the configured behavior of the mock instances and enables the
+ * mock verification. The mocked datasource is also added to a new context.
+ */
+ protected void replayAll()
+ throws Exception {
+ replay(result, statement, connection, datasource);
+ verify = true;
+
+ ContextUtils.createFakeContext(datasource);
+ }
+
+ /**
+ * Clears the context and verifies the mocks if the verification is enabled.
+ *
+ * @throws Exception if an error happens during verification.
+ */
+ @After
+ public void tearDown() throws Exception {
+ ContextUtils.clearContextBuilder();
+
+ try {
+ if (verify) {
+ verify(datasource, connection, statement, result);
+ verify = false;
+ }
+ } finally {
+ datasource = null;
+ connection = null;
+ statement = null;
+ result = null;
+ }
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/util/JSWaiter.java b/src/test/java/es/uvigo/esei/daa/util/JSWaiter.java
new file mode 100644
index 0000000000000000000000000000000000000000..619377457239f3925d40156dfa4a5a011c1587a8
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/util/JSWaiter.java
@@ -0,0 +1,67 @@
+package es.uvigo.esei.daa.util;
+
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebDriverException;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+/**
+ * Utility class to wait for several JavaScript events to complete.
+ *
+ * Code adapted from
+ * https://www.swtestacademy.com/selenium-wait-javascript-angular-ajax/
+ */
+public class JSWaiter {
+ private final WebDriverWait jsWait;
+ private final JavascriptExecutor jsExec;
+
+ private JSWaiter(WebDriver driver) {
+ this.jsWait = new WebDriverWait(driver, 10);
+ this.jsExec = (JavascriptExecutor) driver;
+ }
+
+ public static JSWaiter wait(WebDriver driver) {
+ return new JSWaiter(driver);
+ }
+
+ public void untilAngular5Ready() {
+ try {
+ final Object angular5Check = jsExec.executeScript("return getAllAngularRootElements()[0].attributes['ng-version']");
+ if (angular5Check != null) {
+ final Boolean angularPageLoaded = (Boolean) jsExec
+ .executeScript("return window.getAllAngularTestabilities().findIndex(x=>!x.isStable()) === -1");
+ if (!angularPageLoaded) {
+ poll(20);
+
+ waitForAngular5Load();
+
+ poll(20);
+ }
+ }
+ } catch (WebDriverException ignored) {}
+ }
+
+ private void waitForAngular5Load() {
+ String angularReadyScript = "return window.getAllAngularTestabilities().findIndex(x=>!x.isStable()) === -1";
+
+ try {
+ final ExpectedCondition angularLoad = driver -> Boolean
+ .valueOf(((JavascriptExecutor) driver).executeScript(angularReadyScript).toString());
+
+ final boolean angularReady = Boolean.valueOf(jsExec.executeScript(angularReadyScript).toString());
+
+ if (!angularReady) {
+ jsWait.until(angularLoad);
+ }
+ } catch (WebDriverException ignored) {}
+ }
+
+ private void poll(long milis) {
+ try {
+ Thread.sleep(milis);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java b/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3c924342fccaef458a71b4203d4f8ea0efa8be4
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java
@@ -0,0 +1,134 @@
+package es.uvigo.esei.daa.web;
+
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
+import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
+import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.openqa.selenium.firefox.FirefoxProfile;
+import org.openqa.selenium.html5.LocalStorage;
+import org.springframework.test.context.ContextConfiguration;
+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.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 {
+ private static final int DEFAULT_WAIT_TIME = 5;
+
+ private WebDriver driver;
+ private MainPage mainPage;
+
+ @Before
+ public void setUp() throws Exception {
+ final String baseUrl = "http://localhost:9080/DAAExample/";
+
+ final FirefoxProfile profile = new FirefoxProfile();
+ profile.setPreference("browser.privatebrowsing.autostart", true);
+
+ final FirefoxOptions options = new FirefoxOptions();
+ options.setProfile(profile);
+
+ final FirefoxDriver firefoxDriver;
+ driver = firefoxDriver = new FirefoxDriver();
+ driver.get(baseUrl);
+
+ // Driver will wait DEFAULT_WAIT_TIME if it doesn't find and element.
+ driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
+ driver.manage().window().maximize();
+
+ // Login as "admin:adminpass"
+ final LocalStorage localStorage = firefoxDriver.getLocalStorage();
+ // YWRtaW46YWRtaW5wYXNz
+ localStorage.setItem("user", "{\"login\":\"admin\",\"password\":\"adminpass\"}");
+
+ mainPage = new MainPage(driver, baseUrl);
+ mainPage.navigateTo();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ driver.quit();
+ driver = null;
+ mainPage = null;
+ }
+
+ @Test
+ public void testList() throws Exception {
+ assertThat(mainPage.listPeople(), containsPeopleInAnyOrder(people()));
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-add.xml")
+ public void testAdd() throws Exception {
+ final Person newPerson = mainPage.addPerson(newName(), newSurname());
+
+ assertThat(newPerson, is(equalsToPerson(newPerson())));
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-modify.xml")
+ public void testEdit() throws Exception {
+ final Person person = existentPerson();
+ person.setName(newName());
+ person.setSurname(newSurname());
+
+ mainPage.editPerson(person);
+
+ final Person webPerson = mainPage.getPerson(person.getId());
+
+ assertThat(webPerson, is(equalsToPerson(person)));
+ }
+
+ @Test
+ @ExpectedDatabase("/datasets/dataset-delete.xml")
+ public void testDelete() throws Exception {
+ mainPage.deletePerson(existentId());
+
+ assertFalse(mainPage.hasPerson(existentId()));
+ }
+}
diff --git a/src/test/java/es/uvigo/esei/daa/web/pages/MainPage.java b/src/test/java/es/uvigo/esei/daa/web/pages/MainPage.java
new file mode 100644
index 0000000000000000000000000000000000000000..4170948fc97778693185646e526569587a054380
--- /dev/null
+++ b/src/test/java/es/uvigo/esei/daa/web/pages/MainPage.java
@@ -0,0 +1,242 @@
+package es.uvigo.esei.daa.web.pages;
+
+import static java.util.stream.Collectors.toList;
+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;
+import es.uvigo.esei.daa.util.JSWaiter;
+
+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 + "#/people");
+
+ this.wait.until(presenceOfElementLocated(By.id(TABLE_ID)));
+ }
+
+ public int countPeople() {
+ return new PeopleTable(this.driver).countPeople();
+ }
+
+ public List listPeople() {
+ return new PeopleTable(this.driver).listPeople();
+ }
+
+ 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(Person person) {
+ final PeopleTable table = new PeopleTable(this.driver);
+ table.editPerson(person.getId());
+
+ final PersonForm form = new PersonForm(this.driver);
+ form.setName(person.getName());
+ form.setSurname(person.getSurname());
+ form.submit();
+ }
+
+ public void deletePerson(int id) {
+ final PeopleTable table = new PeopleTable(this.driver);
+
+ table.deletePerson(id);
+
+ JSWaiter.wait(driver).untilAngular5Ready();
+ }
+
+ 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("tbody > 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 List rows = table.findElements(By.cssSelector("tbody > tr"));
+
+ for (WebElement row : rows) {
+ final String rowName = row.findElement(By.className("name")).getText();
+ final String rowSurname = row.findElement(By.className("surname")).getText();
+
+ if (rowName.equals(name) && rowSurname.equals(surname)) {
+ return row;
+ }
+ }
+
+ throw new IllegalArgumentException(String.format("No row found with name '%s' and surname '%s'", name, surname));
+ }
+
+ public int countPeople() {
+ return getRows().size();
+ }
+
+ public List listPeople() {
+ return getRows().stream()
+ .map(this::rowToPerson)
+ .collect(toList());
+ }
+
+ private List getRows() {
+ final String xpathQuery = "//tbody/tr[starts-with(@id, '" + ID_PREFIX + "')]";
+
+ return this.table.findElements(By.xpath(xpathQuery));
+ }
+
+ 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, ""));
+ }
+ }
+}
diff --git a/src/test/resources/contexts/hsql-context.xml b/src/test/resources/contexts/hsql-context.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0e1ec2eef039755bb68f9542a745806772e5cf22
--- /dev/null
+++ b/src/test/resources/contexts/hsql-context.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/contexts/mem-context.xml b/src/test/resources/contexts/mem-context.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a37be3358a4bdc5d6e9e630b12210a2da842f9d2
--- /dev/null
+++ b/src/test/resources/contexts/mem-context.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset-add-pets.xml b/src/test/resources/datasets/dataset-add-pets.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8b4f69cb1c248457f84621928d14e6d20c085677
--- /dev/null
+++ b/src/test/resources/datasets/dataset-add-pets.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset-add.xml b/src/test/resources/datasets/dataset-add.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cc7aaef55159ee148c304e8e31b1177c29bebecd
--- /dev/null
+++ b/src/test/resources/datasets/dataset-add.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset-delete-pets.xml b/src/test/resources/datasets/dataset-delete-pets.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b8ff17bfedfc71464283a09509cd8ad57a012a19
--- /dev/null
+++ b/src/test/resources/datasets/dataset-delete-pets.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset-delete.xml b/src/test/resources/datasets/dataset-delete.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c6cb29362f9731cd7a7f2130b1ce46eade18128f
--- /dev/null
+++ b/src/test/resources/datasets/dataset-delete.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset-modify-pets.xml b/src/test/resources/datasets/dataset-modify-pets.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e07e1a17c9d3996a43a2ee312295680b209f312b
--- /dev/null
+++ b/src/test/resources/datasets/dataset-modify-pets.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset-modify.xml b/src/test/resources/datasets/dataset-modify.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6303bbe49cb0da3d72c86c161746b545132faa51
--- /dev/null
+++ b/src/test/resources/datasets/dataset-modify.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset.dtd b/src/test/resources/datasets/dataset.dtd
new file mode 100644
index 0000000000000000000000000000000000000000..ab6160a35f4a2658f38a549078941fbbbfa1621f
--- /dev/null
+++ b/src/test/resources/datasets/dataset.dtd
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/datasets/dataset.xml b/src/test/resources/datasets/dataset.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f2c544683e0e2bac811636ec81bfb61d11af2c7d
--- /dev/null
+++ b/src/test/resources/datasets/dataset.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/db/hsqldb-drop.sql b/src/test/resources/db/hsqldb-drop.sql
new file mode 100644
index 0000000000000000000000000000000000000000..867d38704d2214a0740cb0555e1c6714cb22dde9
--- /dev/null
+++ b/src/test/resources/db/hsqldb-drop.sql
@@ -0,0 +1,3 @@
+DROP TABLE Pets IF EXISTS;
+DROP TABLE People IF EXISTS;
+DROP TABLE Users IF EXISTS;
diff --git a/src/test/resources/db/hsqldb.sql b/src/test/resources/db/hsqldb.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b01e0f4376b9d0f4148c51aacd63d0c31b66e78d
--- /dev/null
+++ b/src/test/resources/db/hsqldb.sql
@@ -0,0 +1,22 @@
+CREATE TABLE people (
+ id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL,
+ name VARCHAR(50) NOT NULL,
+ surname VARCHAR(100) NOT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE users (
+ login VARCHAR(100) NOT NULL,
+ password VARCHAR(64) NOT NULL,
+ role VARCHAR(5) NOT NULL,
+ PRIMARY KEY (login)
+);
+
+CREATE TABLE pets (
+ id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL,
+ name VARCHAR(64) NOT NULL,
+ owner INTEGER NOT NULL,
+ typeOfAnimal VARCHAR(64) NOT NULL,
+ PRIMARY KEY (id),
+ FOREIGN KEY (owner) REFERENCES people(id) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/src/test/webapp/META-INF/context.xml b/src/test/webapp/META-INF/context.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dbba264d1c4141c9fe643ecd89e1e82e68525c9b
--- /dev/null
+++ b/src/test/webapp/META-INF/context.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/webapp/rest/people/add.html b/src/test/webapp/rest/people/add.html
new file mode 100644
index 0000000000000000000000000000000000000000..c0f7b7df36c086b33d5cd3d060db6152f17887ba
--- /dev/null
+++ b/src/test/webapp/rest/people/add.html
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+add
+
+
+