Информациони системи 1/Лаб 2 2020
Поставка
За део базе података факултета креирати сервис са следећим крајњим тачкама:
- (8 поена)
POST .../ocena/{idPrijave}
- реqуест бодy: (
text/plain
): цео број - респонсе бодy: празно
- Право за извршавање ове методе има само наставник (и админ). Наставник може унети оцену студенту само за предмет који предаје и то за текући испитни рок. Не може се унети оцена за испитни рок за који су још увек отворене пријаве.
- реqуест бодy: (
- (4 поена)
GET .../prosek?idStudenta
- реqуест бодy: празно
- респонсе бодy: (
text/plain
): број - Право за извршавање ове методе има наставник (и админ). Метода враћа просечну оцену студента. Уколико студент нема ниједну оцену ова метода враћа 0.
ЕР дијаграм
На слици је дат модел дела базе података факултета.
Статус у semestar
има вредности:
N
— није у токуP
— омогућена нова праћења предметаT
— у току
Статус у rok
има вредности:
N
— није у токуP
— омогућена пријава предметаT
— у току
СQЛ
Следећа СQЛ скрипта прави базу приказану на дијаграму изнад заједно са подацима који могу да се користе за тестирање.
CREATE TABLE `korisnik` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`korisnicko_ime` VARCHAR(45),
`sifra` VARCHAR(45)
);
CREATE TABLE `admin` (
`korisnik_id` INT PRIMARY KEY,
FOREIGN KEY (`korisnik_id`) REFERENCES `korisnik` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `student` (
`korisnik_id` INT PRIMARY KEY,
`indeks` VARCHAR(45),
`ime_prezime` VARCHAR(45) NOT NULL,
`godina` INT,
FOREIGN KEY (`korisnik_id`) REFERENCES `korisnik` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `nastavnik` (
`korisnik_id` INT PRIMARY KEY,
`ime_prezime` VARCHAR(45),
FOREIGN KEY (`korisnik_id`) REFERENCES `korisnik` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `semestar` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`naziv` VARCHAR(45) NOT NULL,
`status` VARCHAR(1)
);
CREATE TABLE `predmet` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`sifra` VARCHAR(45) NOT NULL,
`naziv` VARCHAR(45) NOT NULL,
`semestar_id` INT NOT NULL,
`godina` INT,
FOREIGN KEY (`semestar_id`) REFERENCES `semestar` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `predaje` (
`nastavnik_korisnik_id` INT,
`predmet_id` INT,
PRIMARY KEY (`nastavnik_korisnik_id`, `predmet_id`),
FOREIGN KEY (`nastavnik_korisnik_id`) REFERENCES `nastavnik` (`korisnik_id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
FOREIGN KEY (`predmet_id`) REFERENCES `predmet` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `prati` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`predmet_id` INT NOT NULL,
`student_korisnik_id` INT NOT NULL,
FOREIGN KEY (`predmet_id`) REFERENCES `predmet` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
FOREIGN KEY (`student_korisnik_id`) REFERENCES `student` (`korisnik_id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `rok` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`naziv` VARCHAR(45) NOT NULL,
`semestar_id` INT NOT NULL,
`status` VARCHAR(1),
FOREIGN KEY (`semestar_id`) REFERENCES `semestar` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `prijava` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`prati_id` INT NOT NULL,
`rok_id` INT NOT NULL,
FOREIGN KEY (`prati_id`) REFERENCES `prati` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
FOREIGN KEY (`rok_id`) REFERENCES `rok` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
CREATE TABLE `ocena` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`ocena` INT,
`prijava_id` INT NOT NULL,
FOREIGN KEY (`prijava_id`) REFERENCES `prijava` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
);
INSERT INTO `korisnik` (`korisnicko_ime`, `sifra`) VALUES
('admin', 'admin'), -- 1
('pera', 'peric'), -- 2
('mika', 'mikic'), -- 3
('zika', 'zikic'), -- 4
('cmilos', 'cmilos'), -- 5
('tasha', 'tasha'), -- 6
('stubic', 'stubic'), -- 7
('tartalja', 'tartalja'); -- 8
INSERT INTO `admin` (`korisnik_id`) VALUES (1);
INSERT INTO `student` (`korisnik_id`, `indeks`, `ime_prezime`, `godina`) VALUES
(2, '0001', 'Pera Perić', 2019),
(3, '0002', 'Mika Mikić', 2020),
(4, '0010', 'Žika Žikić', 2018);
INSERT INTO `nastavnik` (`korisnik_id`, `ime_prezime`) VALUES
(5, 'Miloš Cvetanović'),
(6, 'Tamara Šekularac'),
(7, 'Stefan Tubić'),
(8, 'Igor Tartalja');
INSERT INTO `semestar` (`naziv`, `status`) VALUES
('Peti semestar 2019', 'N'), -- 1
('Peti semestar 2021', 'T'), -- 2
('Treći semestar 2021', 'T'), -- 3
('Drugi semestar 2022', 'P'), -- 4
('Četvrti semestar 2022', 'P'); -- 5
INSERT INTO `predmet` (`sifra`, `naziv`, `semestar_id`, `godina`) VALUES
('13S113IS1', 'Informacioni sistemi 1', 2, 2021), -- 1
('13E114IS1', 'Informacioni sistemi 1', 2, 2021), -- 2
('13S112OO1', 'Objektno orijentisano programiranje 1', 3, 2021), -- 3
('13E112OO1', 'Objektno orijentisano programiranje 1', 3, 2021), -- 4
('13S112OO2', 'Objektno orijentisano programiranje 2', 5, 2022), -- 5
('13E112OO2', 'Objektno orijentisano programiranje 2', 5, 2022); -- 6
INSERT INTO `predaje` (`nastavnik_korisnik_id`, `predmet_id`) VALUES
(8, 3),
(8, 4),
(8, 5),
(8, 6),
(5, 1),
(5, 2),
(6, 1),
(6, 2),
(7, 1),
(7, 2);
INSERT INTO `prati` (`predmet_id`, `student_korisnik_id`) VALUES
(1, 2), -- 1: Pera prati IS1
(2, 3), -- 2: Mika prati IS1
(3, 3), -- 3: Mika prati OO1
(3, 4); -- 4: Žika prati OO1
INSERT INTO `rok` (`naziv`, `semestar_id`, `status`) VALUES
('Januar', 1, 'N'), -- 1
('Januar', 2, 'T'), -- 2
('Januar', 3, 'T'), -- 3
('Februar', 2, 'P'), -- 4
('Februar', 3, 'P'); -- 5
INSERT INTO `prijava` (`prati_id`, `rok_id`) VALUES
(1, 2), -- 1: Pera prijavio IS1 za januar
(1, 4), -- 2: Pera prijavio IS1 za februar
(2, 2), -- 3: Mika prijavio IS1 za februar
(3, 5), -- 4: Mika prijavio OO1 za februar
(4, 3); -- 5: Žika prijavio OO1 za januar
INSERT INTO `ocena` (`ocena`, `prijava_id`) VALUES
(5, 1),
(10, 5);
Решење
persistence.xml
Дефинише јединицу перзистенције mypu
. Претпоставља се постојање ресурса myresource
који је повезан на одговарајући Цоннецтион Поол на Глассфисх.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="mypu" transaction-type="JTA">
<jta-data-source>myresource</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>
</persistence>
models
пакет
Овде су генерисане класе ентитета из базе приложене изнад са подразумеваним подешавањима.
filters
пакет
BasicAuthFilter.java
Филтер који забрањује не-наставницима и не-администраторима приступ, и кроз заглавље X-User-ID
прослеђује ИД пријављеног корисника ресурсу. Могућа ситуација јесте да наставник зада своје X-User-ID
заглавље како би опонашао неког другог наставника па се у ресурсу појаве два X-User-ID
заглавља, и таквим случајем руководимо у ресурсима у којима је битно који је корисник пријављен.
package filters;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import models.Korisnik;
@Provider
public class BasicAuthFilter implements ContainerRequestFilter {
@PersistenceContext(unitName = "mypu")
EntityManager em;
@Override
public void filter(ContainerRequestContext context) throws IOException {
MultivaluedMap<String, String> headers = context.getHeaders();
if (!headers.containsKey("Authorization")) {
context.abortWith(
Response
.status(Response.Status.UNAUTHORIZED)
.entity("Korisničko ime i lozinka nisu prosleđeni.")
.build()
);
return;
}
List<String> authHeaders = context.getHeaders().get("Authorization");
if (authHeaders.isEmpty()) {
context.abortWith(
Response
.status(Response.Status.UNAUTHORIZED)
.entity("Korisničko ime i lozinka nisu prosleđeni.")
.build()
);
return;
}
String[] authorization = new String(Base64.getDecoder().decode(authHeaders.get(0).replace("Basic ", "")), StandardCharsets.UTF_8).split(":");
if (authorization.length != 2) {
context.abortWith(
Response
.status(Response.Status.BAD_REQUEST)
.entity("Pogrešno prosleđeno korisničko ime ili lozinka.")
.build()
);
return;
}
String username = authorization[0];
String password = authorization[1];
List<Korisnik> korisnici = em.createNamedQuery("Korisnik.findByKorisnickoIme", Korisnik.class)
.setParameter("korisnickoIme", username)
.getResultList();
if (korisnici.isEmpty() || !korisnici.get(0).getSifra().equals(password)) {
context.abortWith(
Response
.status(Response.Status.BAD_REQUEST)
.entity("Pogrešno korisničko ime ili lozinka.")
.build()
);
return;
}
Korisnik korisnik = korisnici.get(0);
if (korisnik.getNastavnik() == null && korisnik.getAdmin() == null) {
context.abortWith(
Response
.status(Response.Status.FORBIDDEN)
.entity("Niste nastavnik niti administrator.")
.build()
);
return;
}
// Pass headers to resources.
context.getHeaders().add("X-User-ID", korisnik.getId().toString());
}
}
resources
пакет
OcenaResource.java
package resources;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import models.Admin;
import models.Nastavnik;
import models.Ocena;
import models.Prati;
import models.Predmet;
import models.Prijava;
import models.Rok;
@Path("ocena")
@Stateless
public class OcenaResource {
@PersistenceContext(unitName = "mypu")
EntityManager em;
@POST
@Path("{idPrijave}")
public Response oceni(@PathParam("idPrijave") int idPrijave, @Context HttpHeaders headers, String body) {
// Get authenticated user.
List<String> idHeaders = headers.getRequestHeaders().get("X-User-ID");
if (idHeaders.size() != 1) {
// The user may have passed our internal header in the request as
// an attempt to identify as someone else.
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Zaglavlje X-User-ID je interno i ne sme se slati u zahtevima.")
.build();
}
int korisnikId = Integer.parseInt(idHeaders.get(0));
List<Prijava> prijave = em.createNamedQuery("Prijava.findById", Prijava.class)
.setParameter("id", idPrijave)
.getResultList();
if (prijave.isEmpty()) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Nije validan ID prijave.")
.build();
}
Prijava prijava = prijave.get(0);
Rok rok = prijava.getRokId();
if (!rok.getStatus().equals("T")) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Ne može se oceniti ispit u roku za koji su još uvek otvorene prijave.")
.build();
}
Prati prati = prijava.getPratiId();
Predmet predmet = prati.getPredmetId();
List<Nastavnik> nastavnici = predmet.getNastavnikList();
boolean found = false;
for (Nastavnik nastavnik : nastavnici) {
if (nastavnik.getKorisnikId() == korisnikId) {
found = true;
break;
}
}
if (!found) {
boolean isNotAdmin = em.createNamedQuery("Admin.findByKorisnikId", Admin.class)
.setParameter("korisnikId", korisnikId)
.getResultList()
.isEmpty();
if (isNotAdmin) {
return Response
.status(Response.Status.FORBIDDEN)
.entity("Niste nastavnik na tom predmetu.")
.build();
}
}
// Duplicate grades not handled.
Ocena ocena = new Ocena();
ocena.setOcena(Integer.parseInt(body));
ocena.setPrijavaId(prijava);
em.persist(ocena);
return Response
.noContent()
.build();
}
}
ProsekResource.java
package resources;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import models.Ocena;
import models.Prati;
import models.Prijava;
import models.Student;
@Path("prosek")
@Stateless
public class ProsekResource {
@PersistenceContext(unitName = "mypu")
EntityManager em;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response prosek(@QueryParam("idStudenta") int idStudenta) {
List<Student> studenti = em.createNamedQuery("Student.findByKorisnikId", Student.class)
.setParameter("korisnikId", idStudenta)
.getResultList();
if (studenti.isEmpty()) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Ne postoji student sa zadatim ID.")
.build();
}
int sum = 0;
int count = 0;
double prosek = 0;
Student student = studenti.get(0);
for (Prati prati : student.getPratiList()) {
int maxOcenaId = 0;
int maxOcena = 0;
for (Prijava prijava : prati.getPrijavaList()) {
for (Ocena ocena : prijava.getOcenaList()) {
if (ocena.getOcena() > 5 && ocena.getId() > maxOcenaId) {
maxOcenaId = ocena.getId();
maxOcena = ocena.getOcena();
}
}
}
if (maxOcena > 0) {
sum += maxOcena;
++count;
}
}
if (count != 0) {
prosek = sum/count;
}
return Response
.ok(prosek)
.build();
}
}