Informacioni sistemi 1/Lab 2 2020

Izvor: SI Wiki
Pređi na navigaciju Pređi na pretragu

Postavka

Za deo baze podataka fakulteta kreirati servis sa sledećim krajnjim tačkama:

  • (8 poena) POST .../ocena/{idPrijave}
    • request body: (text/plain): ceo broj
    • response body: prazno
    • Pravo za izvršavanje ove metode ima samo nastavnik (i admin). Nastavnik može uneti ocenu studentu samo za predmet koji predaje i to za tekući ispitni rok. Ne može se uneti ocena za ispitni rok za koji su još uvek otvorene prijave.
  • (4 poena) GET .../prosek?idStudenta
    • request body: prazno
    • response body: (text/plain): broj
    • Pravo za izvršavanje ove metode ima nastavnik (i admin). Metoda vraća prosečnu ocenu studenta. Ukoliko student nema nijednu ocenu ova metoda vraća 0.

ER dijagram

Na slici je dat model dela baze podataka fakulteta.

Дати ЕР дијаграм базе података.

Status u semestar ima vrednosti:

  • N — nije u toku
  • P — omogućena nova praćenja predmeta
  • T — u toku

Status u rok ima vrednosti:

  • N — nije u toku
  • P — omogućena prijava predmeta
  • T — 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 myresource 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>myresource</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 zabranjuje ne-nastavnicima i ne-administratorima pristup, i kroz zaglavlje X-User-ID prosleđuje ID prijavljenog korisnika resursu. Moguća situacija jeste da nastavnik zada svoje X-User-ID zaglavlje kako bi oponašao nekog drugog nastavnika pa se u resursu pojave dva X-User-ID zaglavlja, i takvim slučajem rukovodimo u resursima u kojima je bitno koji je korisnik prijavljen.

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 paket

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();
    }
}