Informacioni sistemi 1/Lab 2 2022
Druga laboratorijska vežba 2022. godine održana je u januaru 2022. godine i grupe su bile skoro identične, sa malim razlikama u prvoj stavci. Baza je takođe bila identična kao prethodnih godina.
Postavka
Za deo baze podataka fakulteta kreirati servis sa sledećim krajnjim tačkama:
- (8 poena)
POST .../prijava/{idPredmeta}
- request body: prazno
- response body: prazno
- Pravo za izvršavanje ove metode ima samo student. Student može da prijavi predmet samo ukoliko prati taj predmet, rok je otvoren za prijavu predmeta i rok prijave predmeta je u istom semestru kao i sam predmet.
- (4 poena)
GET .../prijave/{idPredmeta}?idRoka
- request body: prazno
- response body: (
text/xml
) Sve prijave ispita u sledećem formatu:<prijave> <prijava> <imePrezimeStudenta>Ana Anić</imePrezimeStudenta> <brojIndeksaStudenta>2010/0001</brojIndeksaStudenta> <sifraPredmeta>P1</sifraPredmeta> <nazivPredmeta>Programiranje 1</nazivPredmeta> <nazivRoka>januar 2010</nazivRoka> </prijava> ... </prijave>
- Pravo za izvršavanje ove metode ima nastavnik. Metoda vraća sve prijave ispita u jednom roku u formatu iznad. Ukoliko je zadat i parametar
idRoka
, prikazuje samo prijave za zadati rok.
ER dijagram
- Baza i skripta ispod su identični kao na drugoj laboratorijskoj vežbi 2020. godine.
Na slici je dat model dela baze podataka fakulteta.
Status u semestar
ima vrednosti:
N
— nije u tokuP
— omogućena nova praćenja predmetaT
— u toku
Status u rok
ima vrednosti:
N
— nije u tokuP
— omogućena prijava predmetaT
— u toku
SQL
Sledeća SQL skripta pravi bazu prikazanu na dijagramu iznad zajedno sa podacima koji mogu da se koriste za testiranje.
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);
Rešenje
persistence.xml
Definiše jedinicu perzistencije mypu
. Pretpostavlja se postojanje resursa fakultetResource
koji je povezan na odgovarajući Connection Pool na Glassfish.
<?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>fakultetResource</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>
</persistence>
models
paket
Ovde su generisane klase entiteta iz baze priložene iznad sa podrazumevanim podešavanjima.
filters
paket
BasicAuthFilter.java
Filter koji proverava da li je korisnik ulogovan i zabranjuje pristup ukoliko nije, a kroz zaglavlja X-User-ID
i X-User-Role
prosleđuje ID i ulogu prijavljenog korisnika resursu, respektivno. Moguća situacija jeste da korisnik zada svoje X-User-ID
ili X-User-Role
zaglavlje kako bi oponašao nekog nastavnika pa se u resursu pojave dva takva zaglavlja, i takvim slučajem rukovodimo u resursima u kojima je bitno koji je korisnik prijavljen, odnosno koja je njegova uloga.
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 {
context.getHeaders().getFirst("Authorization");
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;
}
// Pass headers to resources.
String role = "none";
Korisnik korisnik = korisnici.get(0);
if (korisnik.getAdmin() != null) {
role = "admin";
} else if (korisnik.getNastavnik() != null) {
role = "nastavnik";
} else if (korisnik.getStudent() != null) {
role = "student";
}
context.getHeaders().add("X-User-ID", korisnik.getId().toString());
context.getHeaders().add("X-User-Role", role);
}
}
resources
paket
PrijavaResource.java
Rukovodi zahtevom iz prvog zadatka.
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.Prati;
import models.Predmet;
import models.Prijava;
import models.Rok;
@Stateless
@Path("prijava")
public class PrijavaResource {
@PersistenceContext(unitName = "mypu")
EntityManager em;
@POST
@Path("{idPredmeta}")
public Response prijavi(@PathParam("idPredmeta") int idPredmeta, @Context HttpHeaders headers) {
List<String> idHeaders = headers.getRequestHeaders().get("X-User-ID");
List<String> roleHeaders = headers.getRequestHeaders().get("X-User-Role");
if (idHeaders.size() != 1 || roleHeaders.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("Zaglavlja X-User-ID i X-User-Role su interna i ne smeju se slati u zahtevima.")
.build();
}
if (!roleHeaders.get(0).equals("student")) {
return Response
.status(Response.Status.FORBIDDEN)
.entity("Morate biti student.")
.build();
}
List<Rok> rokovi = em
.createNamedQuery("Rok.findByStatus")
.setParameter("status", "P")
.getResultList();
if (rokovi.isEmpty()) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Prijava nije u toku.")
.build();
}
Rok rok = rokovi.get(0);
int idStudenta = Integer.parseInt(idHeaders.get(0));
Predmet predmet = em.find(Predmet.class, idPredmeta);
if (predmet == null) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Ne postoji zadati predmet.")
.build();
}
Prati prati = null;
for (Prati p : predmet.getPratiList()) {
if (p.getStudentKorisnikId().getKorisnikId() == idStudenta) {
prati = p;
break;
}
}
if (prati == null) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Student ne prati predmet.")
.build();
}
if (!predmet.getSemestarId().equals(rok.getSemestarId())) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Predmet i rok nisu u istom semestru.")
.build();
}
for (Prijava prijava : prati.getPrijavaList()) {
if (prijava.getRokId().equals(rok)) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Student je već prijavljen.")
.build();
}
}
Prijava prijava = new Prijava();
prijava.setPratiId(prati);
prijava.setRokId(rok);
em.persist(prijava);
return Response.noContent().build();
}
}
PrijaveResource.java
Rukovodi zahtevom iz drugog zadatka.
package resources;
import java.util.ArrayList;
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.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import models.Predmet;
import responses.PrijavaResponse;
import responses.PrijaveResponse;
@Stateless
@Path("prijave")
public class PrijaveResource {
@PersistenceContext(unitName = "mypu")
EntityManager em;
@GET
@Path("{idPredmeta}")
@Produces(MediaType.TEXT_XML)
public Response getPrijave(@PathParam("idPredmeta") int idPredmeta, @QueryParam("idRoka") Integer idRoka, @Context HttpHeaders headers) {
List<String> roleHeaders = headers.getRequestHeaders().get("X-User-Role");
if (roleHeaders.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-Role je interno i ne sme se slati u zahtevima.")
.build();
}
if (!roleHeaders.get(0).equals("nastavnik")) {
return Response
.status(Response.Status.FORBIDDEN)
.entity("Morate biti nastavnik.")
.build();
}
Predmet predmet = em.find(Predmet.class, idPredmeta);
if (predmet == null) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Ne postoji zadati predmet.")
.build();
}
List<PrijavaResponse> prijave = new ArrayList<>();
predmet.getPratiList().forEach(prati -> {
prati.getPrijavaList().forEach(prijava -> {
if (idRoka != null && !prijava.getRokId().getId().equals(idRoka)) {
return;
}
prijave.add(
new PrijavaResponse()
.setImePrezimeStudenta(prati.getStudentKorisnikId().getImePrezime())
.setBrojIndeksaStudenta(prati.getStudentKorisnikId().getIndeks())
.setSifraPredmeta(predmet.getSifra())
.setNazivPredmeta(predmet.getNaziv())
.setNazivRoka(prijava.getRokId().getNaziv())
);
});
});
PrijaveResponse prijaveResponse = new PrijaveResponse();
prijaveResponse.setPrijave(prijave);
return Response
.ok(prijaveResponse)
.build();
}
}
responses
paket
PrijavaResponse.java
Koristi se kao element liste u PrijaveResponse
klasi, odnosno sadrži sve potrebne informacije o jednoj prijavi koje treba da se vrate iz druge stavke zadatka.
package responses;
public class PrijavaResponse {
private String imePrezimeStudenta;
private String brojIndeksaStudenta;
private String sifraPredmeta;
private String nazivPredmeta;
private String nazivRoka;
public String getImePrezimeStudenta() {
return imePrezimeStudenta;
}
public PrijavaResponse setImePrezimeStudenta(String imePrezimeStudenta) {
this.imePrezimeStudenta = imePrezimeStudenta;
return this;
}
public String getBrojIndeksaStudenta() {
return brojIndeksaStudenta;
}
public PrijavaResponse setBrojIndeksaStudenta(String brojIndeksaStudenta) {
this.brojIndeksaStudenta = brojIndeksaStudenta;
return this;
}
public String getSifraPredmeta() {
return sifraPredmeta;
}
public PrijavaResponse setSifraPredmeta(String sifraPredmeta) {
this.sifraPredmeta = sifraPredmeta;
return this;
}
public String getNazivPredmeta() {
return nazivPredmeta;
}
public PrijavaResponse setNazivPredmeta(String nazivPredmeta) {
this.nazivPredmeta = nazivPredmeta;
return this;
}
public String getNazivRoka() {
return nazivRoka;
}
public PrijavaResponse setNazivRoka(String nazivRoka) {
this.nazivRoka = nazivRoka;
return this;
}
}
PrijaveResponse.java
Koristi se za formatiranje odgovora iz druge stavke zadatka kao XML, tako da individualne prijave budu unutar <prijava>
a oko njih da bude <prijave>
.
package responses;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "prijave")
public class PrijaveResponse {
private List<PrijavaResponse> prijave;
@XmlElement(name = "prijava")
public List<PrijavaResponse> getPrijave() {
return prijave;
}
public void setPrijave(List<PrijavaResponse> prijave) {
this.prijave = prijave;
}
}