Информациони системи 1/Лаб 2 2022 јануар
Prva nadoknada druge laboratorijske vežbe 2022. godine održana je početkom februara 2022. godine. 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, za tekući rok je omogućena prijava predmeta i rok prijave predmeta je u istom semestru kao i sam predmet.
- (4 poena)
POST .../admin/- request body: (
text/xml) Sve informacije su u sledećem formatu:<korisnik> <imeKor>...</imeKor> <sifra>...</sifra> </korisnik>
- response body: prazno
- Pravo za izvršavanje ove metode ima samo admin. Metoda kreira novog admina sa datim informacijama. Nije dozvoljeno da postoji više od jednog admina sa istim korisničkim imenom.
- request body: (
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 zaglavlje X-User-ID prosleđuje ID prijavljenog korisnika resursu. Moguća situacija jeste da korisnik zada svoje X-User-ID 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. Filter takođe proverava kojem se resursu pristupa i odbija zahtev ukoliko se pristupa resursu kome trenutna uloga ne dozvoljava pristup.
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.
Korisnik korisnik = korisnici.get(0);
String endpoint = context
.getUriInfo()
.getPathSegments()
.get(0)
.getPath();
if (
(endpoint.equals("prijava") && korisnik.getStudent() == null) ||
(endpoint.equals("admin") && korisnik.getAdmin() == null)
) {
context.abortWith(
Response
.status(Response.Status.FORBIDDEN)
.entity("Nije vam dozvoljen pristup ovom resursu.")
.build()
);
return;
}
context.getHeaders().add("X-User-ID", korisnik.getId().toString());
}
}
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");
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();
}
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();
}
}
AdminResource.java
Rukovodi zahtevom iz drugog 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.core.Response;
import models.Admin;
import models.Korisnik;
import requests.CreateAdminRequest;
@Stateless
@Path("admin")
public class AdminResource {
@PersistenceContext(unitName = "mypu")
EntityManager em;
@POST
public Response napraviAdmina(CreateAdminRequest request) {
if (request == null) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Podaci o novom adminu nisu ispravno prosleđeni.")
.build();
}
List<Korisnik> korisnici = em
.createNamedQuery("Korisnik.findByKorisnickoIme")
.setParameter("korisnickoIme", request.imeKor)
.getResultList();
if (korisnici.stream().anyMatch(k -> k.getAdmin() != null)) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Admin sa zadatim korisničkim imenom već postoji.")
.build();
}
Korisnik noviKorisnik = new Korisnik();
noviKorisnik.setKorisnickoIme(request.imeKor);
noviKorisnik.setSifra(request.sifra);
em.persist(noviKorisnik);
// We're flushing writes to the database so JPA updates our User object
// and we can successfully obtain the new ID.
em.flush();
Admin noviAdmin = new Admin();
noviAdmin.setKorisnikId(noviKorisnik.getId());
em.persist(noviAdmin);
return Response
.status(Response.Status.NO_CONTENT)
.build();
}
}
requests paket
CreateAdminRequest.java
Služi za deserijalizaciju tela zahteva za kreiranje admina, kako bi iz ovog objekta mogli da se vade podaci tog zahteva.
package requests;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "korisnik")
public class CreateAdminRequest {
public String imeKor;
public String sifra;
}