home_site

Lab04 - REST JAX-RS [ ver. ZTI.2025.04.04.006 ]

Zawartość strony

Zadania realizowane w ramach laboratorium

Skrypty opracowane w ramach zajęć.

  1. Aplikacje RESTful.
  2. Obsługa RESTful - curl
  3. Narzędzie Chrome, Advanced REST client
  4. Aplikacja RESTful i JPA
  5. RESTful klient
  6. CORS, Cross-Origin Resource Sharing
  7. Log - logowanie żądań i odpowiedzi
  8. OpenAPI, Swagger
  1. Aplikacja RESTful

    1. W ramach tego zadania zostanie opracowana część serwerowa aplikacji zgodna z stylem REST. W ramach technologii Jakarta EE dostępny jest pakiet JAX-RS umożliwiający szybkie budowanie aplikacji zgodnej z stylem REST. Pakiet JAX-RS dostępny jest w pełnej wersji Jakarta EE (JEE) i na serwerach aplikacyjnych. Do realizacji projektu w kontenerze Apache Tomcat wymagana jest instalacja pakietu Apache Jersey implementującego interfejs JAX-RS.
      Opracowany w trakcie zajęć serwer realizuje zadania CRUD (create, read, update, delete) w relacyjnej bazie danych wykorzystując poznane wcześniej technologie JNDI i JPA.
    2. W ramach środowiska IDE IntelliJ utworzymy projekt z wykorzystaniem szablonu REST-service, który zainstaluje opowiednie pakiety implementujące technologię JAX-RS w projekcie. Projekt realizujemy z wykorzystaniem serwera TomEE. Na serwerze Tomcat wymagana jest implementacja Eclipse Jersey Server i Weld SE.
    3. Tworzymy projekt Lab04 w ramach IDEA - rys.1 i rys.2.
      Lab04_rest_1
      Rys.1 Konfiguracja projektu REST w IDE IntelliJ
      Lab04_rest_2
      Rys.2 Konfiguracja projektu REST w IDE IntelliJ
    4. Przygotowany projekt jest gotowy do uruchomienia. W ramach tego rozwiązania wykorzystywana jest abstrakcyjna klasa Application, która została rozszerzona przez klasę HelloApplication. Właściwa klasa realizującą funkcjonalność aplikacji została zaimplemetowana w klasie HelloResource. Proponuję zmienić nazwę klasy HelloApplication na Lab04Application i uruchomić projekt.
    5. Poprawnie przygotowany projekt powinien zwrócić wynik przedstawiony na rys.3.
      Lab04_JAX-RS
      Rys.3 Test projektu w ramach RESTful for Web Service (JAX-RS)
    6. Kolejnym etapem, będzie realizacja prostego serwisu CRUD w stylu REST obsługującego bazę danych utworzoną na poprzednich zajęciach w bazie PostgreSQL dostęnej na serwerze Pascal. Do realizacji zadania wykorzystamy przedstawione poniżej klasy:
      • klasa JDBCResource.java realizująca żądania poprzez interfejs REST (GET, POST, PUT i DELETE),
      • klasa Person.java typu POJO obsługująca dane przechowywane w tabeli Person,
      • klasa Database.java obsługująca żądania CRUD do bazy danych (interfejs JDBC konfigurowany poprzez JNDI).
    7. Klasa opisująca żądania obsługiwane przez aplikację (GET, POST, PUT i DELETE) (w ramach pakietu zti.lab04).

      Klasa JDBCResource.java - ( [listing dokumentu] [link do dokumentu]

         package zti.lab04;
      
      import jakarta.ws.rs.Consumes;
      import jakarta.ws.rs.DELETE;
      import jakarta.ws.rs.GET;
      import jakarta.ws.rs.POST;
      import jakarta.ws.rs.PUT;
      import jakarta.ws.rs.Path;
      import jakarta.ws.rs.PathParam;
      import jakarta.ws.rs.Produces;
      import jakarta.ws.rs.core.MediaType;
      
      import zti.model1.Database;
      import zti.model1.Person;
      
      @Path("/jdbc/person")
      public class JDBCResource {
      
          @GET
          @Produces({MediaType.APPLICATION_JSON})
          public Person[] get() {
              System.out.println("GET");
              Database data = new Database();
              return data.getPersonList().toArray(new Person[0]);
          }
      
          @GET
          @Path("{id}")
          @Produces({MediaType.APPLICATION_JSON})
          public Person get(@PathParam("id") String id) {
              System.out.println("GET");
              Database data = new Database();
              return data.readPerson(id);
          }
      
          @POST
          @Consumes({MediaType.APPLICATION_JSON})
          @Produces({MediaType.TEXT_PLAIN})
          public String post(Person person) {
              System.out.println("POST");
              Database data = new Database();
              data.createPerson(person);
              return "add record" ;
          }
      
          @PUT
          @Consumes({MediaType.APPLICATION_JSON})
          @Produces({MediaType.TEXT_PLAIN})
          public String put(Person person) {
              System.out.println("PUT");
              Database data = new Database();
              data.updatePerson(person);
              return "update record" ;
          }
      
          @DELETE
          @Path("{id}")
          @Produces({MediaType.TEXT_PLAIN})
          public String delete(@PathParam("id") String id) {
              System.out.println("DELETE");
              Database data = new Database();
              data.deletePerson(id);
              return "delete record" ;
          }
      
      }
      
    8. Klasa obsługująca dane z tabeli PERSON w bazie danych - pakiet zti.model1.

      Klasa Person.java - ( [listing dokumentu] [link do dokumentu]

      package zti.model1;
      
      public class Person {
      	
        private String lname ;
        private String fname ;
        private String city ;
        private String email ;
        private String tel ;
        private Integer id;
      
        public Person() {}
        
        public Person(String lname, String fname, String city, String email, String tel, Integer id){
      		this.lname = lname;
      		this.fname = fname;
      		this.city = city;
      		this.email = email;
      		this.tel = tel;		
      		this.id = id;
        }
        
        public void setFname( String newValue ){ fname = newValue ; }
        public String getFname() { return fname ; }
        public void setLname ( String newValue ) { lname = newValue ; }
        public String getLname() { return lname ; }
        public void setCity ( String newValue ) { city = newValue ; }
        public String getCity() { return city ; } 
        public void setEmail ( String newValue ) { email = newValue ; }
        public String getEmail() { return email ; }    
        public void setTel ( String newValue ) { tel = newValue ; }
        public String getTel() { return tel ; }    
        public void setId ( Integer newValue ) { id = newValue ; }  
        public Integer getId() { return id ; }    
      
      }
      
    9. Klasa obsługująca żądania SQL do bazy danych - pakiet zti.model1.

      Klasa Database.java - ( [listing dokumentu] [link do dokumentu]

      package zti.model1;
      
      import javax.naming.Context;
      import javax.naming.InitialContext;
      import javax.naming.NamingException;
      import javax.sql.DataSource;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.sql.Statement;
      import java.util.ArrayList;
      import java.util.List;
      
      
      
      
      public class Database {
      
          private Person person;
          private Connection conn = null;
          private PreparedStatement prestmt = null;
          private Statement stmt = null;
          private ResultSet rset = null;
          private List<Person> list = new ArrayList<Person>();
      
          public Database() {
              try {
                  Context context = new InitialContext();
                  DataSource ds1 = (DataSource) context.lookup("java:comp/env/jdbc/postgres");
                  conn = ds1.getConnection();
              } catch (NamingException ex) {
                  System.out.println(ex.getMessage());
              } catch (SQLException ex) {
                  System.out.println(ex.getMessage());
              }
          }
      
          public void createPerson(Person person) {
              try {
                  prestmt = conn.prepareStatement("INSERT INTO person (fname, lname, email, city, tel) VALUES (?,?,?,?, ?)");
                  prestmt.setString(1, person.getFname());
                  prestmt.setString(2, person.getLname());
                  prestmt.setString(3, person.getEmail());
                  prestmt.setString(4, person.getCity());
                  prestmt.setString(5, person.getTel());
                  prestmt.executeUpdate();
              } catch (SQLException ex) {
                  System.out.println(ex.getMessage());
              } finally {
                  try {
                      if (prestmt != null) {
                          prestmt.close();
                      }
                      if (conn != null) {
                          conn.close();
                      }
                  } catch (SQLException ex) {
                      System.out.println(ex.getMessage());
                  }
              }
              return;
          }
      
          public void deletePerson(String id) {
              try {
                  // System.out.println("Deleted record" + id);
                  Integer ident = Integer.parseInt(id);
                  prestmt = conn.prepareStatement("DELETE FROM person WHERE id = ?");
                  prestmt.setInt(1, ident);
                  prestmt.executeUpdate();
              } catch (SQLException ex) {
                  System.out.println(ex.getMessage());
              } finally {
                  try {
                      if (prestmt != null) {
                          prestmt.close();
                      }
                      if (conn != null) {
                          conn.close();
                      }
                  } catch (SQLException ex) {
                      System.out.println(ex.getMessage());
                  }
              }
              return;
          }
      
          public Person readPerson(String id) {
              try {
                  Integer ident = Integer.parseInt(id);
                  prestmt = conn.prepareStatement("SELECT * FROM person WHERE id = ? ");
                  prestmt.setInt(1, ident);
                  ResultSet rset = prestmt.executeQuery();
                  person = new Person();
                  while (rset.next()) {
                      person.setFname(rset.getString("fname"));
                      person.setLname(rset.getString("lname"));
                      person.setEmail(rset.getString("email"));
                      person.setTel(rset.getString("tel"));
                      person.setCity(rset.getString("city"));
                      person.setId(rset.getInt("id"));
                  }
              } catch (SQLException ex) {
                  System.out.println(ex.getMessage());
              } finally {
                  try {
                      if (prestmt != null) {
                          prestmt.close();
                      }
                      if (conn != null) {
                          conn.close();
                      }
                  } catch (SQLException ex) {
                      System.out.println(ex.getMessage());
                  }
              }
              return person;
          }
      
          public void updatePerson(Person person) {
              try {
                  // System.out.println("Deleted record" + person.getId());
                  prestmt = conn.prepareStatement("UPDATE person SET fname=?,lname=?,email=?,city=?,tel=?  WHERE id = ?");
                  prestmt.setString(1, person.getFname());
                  prestmt.setString(2, person.getLname());
                  prestmt.setString(3, person.getEmail());
                  prestmt.setString(4, person.getCity());
                  prestmt.setString(5, person.getTel());
                  prestmt.setInt(6, person.getId());
                  prestmt.executeUpdate();
              } catch (SQLException ex) {
                  System.out.println(ex.getMessage());
              } finally {
                  try {
                      if (prestmt != null) {
                          prestmt.close();
                      }
                      if (conn != null) {
                          conn.close();
                      }
                  } catch (SQLException ex) {
                      System.out.println(ex.getMessage());
                  }
              }
              return;
          }
      
          public  List<Person> getPersonList() {
              try {
                  stmt = conn.createStatement();
                  String sql = "SELECT * FROM public.person ORDER BY lname";
                  rset = stmt.executeQuery(sql);
                  while (rset.next()) {
                      person = new Person();
                      person.setFname(rset.getString("fname"));
                      person.setLname(rset.getString("lname"));
                      person.setEmail(rset.getString("email"));
                      person.setTel(rset.getString("tel"));
                      person.setCity(rset.getString("city"));
                      person.setId(rset.getInt("id"));
                      list.add(person);
                  }
                  rset.close();
              } catch (SQLException ex) {
                  System.out.println(ex.getMessage());
              } finally {
                  try {
                      if (prestmt != null) {
                          prestmt.close();
                      }
                      if (conn != null) {
                          conn.close();
                      }
                  } catch (SQLException ex) {
                      System.out.println(ex.getMessage());
                  }
              }
              return list;
          }
      
      }
      
      
    10. Na koniec modyfikujemy klasę Lab04Application dodając poniższą funkcjonalność w ramach klasy Lab04Application.
          public Lab04Application() {}
      
          public Set<Class<?>> getClasses() {
              HashSet<Class<?>> set = new HashSet<Class<?>>();
              set.add(HelloResource.class);
              set.add(JDBCResource.class);
              return set;
          }
      
    11. Poprawne działanie aplikacji wymaga jeszcze realizacji następujących punktów:
      • udostępnienia sterownika do bazy Postgresql - dodanie zależności w pliku pom.xml,
      • utworzenie pliku context.xml w katalogu webapp/META-INF,
      Realizacja powyższym zadań zgodnie z informacjami przekazanymi w labororium 3.
    12. Uruchamiamy aplikację. W ramach przeglądarki WWW możliwe jest tylko zrealizowanie zapytania http get. Opracowany serwer udostępnia dwa polecenia zwracające zawartość bazy danych.
      • http://localhost:<port>/<context>/api/jdbc/person/ - zwraca wszystkie rekordy z bazy danych w formacie JSON (zapis do pliku)
      • http://localhost:<port>/<context>/api/jdbc/person/{id} - zwraca jeden rekord o podanym identyfikatorze w formacie JSON
    13. Na rys. 4 przedstawiono przykładową odpowiedź serwisu.
      Lab04_crud
      Rys.4 Przykładowa odpowiedź serwisu.
    14. Pozostałe metody protokołu http nie są bezpośrednio obsługiwane w ramach przeglądarki i wymagają dodatkowych narzędzi. Opracowany serwis przetestujemy z wykorzystaniem polecenia curl, aplikacji Advanced REST client - narzędzia dostępnego w przeglądarce Chrome oraz aplikacji realizującej klienta stylu REST w Javie.
  2. Obsługa RESTful - curl

    1. Na początek przetestujemy naszą aplikację wykorzystując bibliotekę cURL. Jest to biblioteka napisana w języku C umożliwiająca testowanie różnych serwisów internetowych w tym serwisów WWW. Umożliwia wysyłanie zapytań HTTP zgodnie z metodami ( GET, POST, PUT czy DELETE), odbiór zwróconych stron czy plików. W ramach serwisu WWW obsługuje certyfikaty SSL, uwierzytelnianie użytkowników, obsługę wierszy żądania HTTP oraz kodowania MIME.
    2. W ramach systemu Linux dostępne jest polecenie curl realizujące przedstawioną powyżej funkcjonalność.
    3. Po rozpakowaniu w katalogu bin znajdziemy interesująca nas aplikację.
    4. Wszystkie rekordy z bazy danych pobieramy poniższym poleceniem.
      curl -X GET http://localhost:<port>/<context>/api/jdbc/person/
      
    5. Dowolny rekord z bazy danych wybieramy zapytaniem przedstawionym poniżej.
      curl -X GET http://localhost:<port>/<context>/api/jdbc/person/3
      
    6. Dodanie rekordu do bazy danych przedstawia poniższe polecenie.
      curl -H "Content-type: application/json" -d "@dane.json" -X POST http://localhost:<port>/<context>/api/jdbc/person/
      
      Uwaga. Rekord dodajemy z pliku dane.json zawierającego wprowadzane dane.
      {
          "fname":    "Test",
          "lname":    "Net",
          "city":     "Example",
          "email":    "test.net@example.org",
          "tel":        "12-345-67890"
      }
      
    7. Usunięcie rekordu z bazy danych wykonujemy przedstawionym poniżej poleceniem.
      curl -X DELETE http://localhost:<port>/<context>/api/jdbc/person/{numer}
      
    8. Na koniec poprawa rekordu w bazie danych z wykorzystaniem narzędzia cURL.
      curl -H "Content-type: application/json" -d "@dane.json" -X PUT http://localhost:<port>/<context>/api/jdbc/person/
      
      Plik z danymi do modyfikacji w bazie danych - zawiera atrybut "id". Zapisujemy nasze dane - dane.json.
      {"id":"26", "fname":"Marek","lname":"Ebacki","email":"ebacki@wp.pl","city":"Rzeszow","tel":"31 100 0000"}
      
    9. Przykładowe polecenia curl przedstawione zostały na rys.5.
      Lab04_curl
      Rys.5 Przykładowe polecenia curl.
  3. Narzędzie Chrome, Advanced REST client

    1. Przeglądarka Chrome udostępnia graficzne narzędzie do testowania serwisu REST. Po zainstalowaniu przeglądarki Chrome, instalujemy rozszerzenie Advanced REST client. Po uruchomieniu aplikacji (chrome://apps) otrzymamy graficzny interfejs do testowania aplikacji przedstawiony na rys.6. Poniżej polecenia do instalacji oprogramowania na stacjach.
      $ sudo apt-get install chromium
      Strona WWW https://install.advancedrestclient.com/ - Download arc-linux-17.0.9-amd64.deb 79.1 MB Mar 3, 2022 
      $ sudo apt-get install ../Downloads/arc-linux-17.0.9-amd64.deb
      
      Lab04_Chrome_ARC
      Rys.6 Testowanie aplikacji REST przy pomocy narzędzia Chrome ARC
  4. Aplikacja RESTful i JPA

    1. W kolejnym punkcie opracujemy serwis RESTful realizujący CRUD na relacyjnej bazie danych wykorzystujący interfejs JPA. W zadaniu wykorzystamy konfigurację dostępu do bazy danych JNDI. Realizacja tego punktu wymaga odpowiedniego przygotowania projektu:
      • obsługa funkcjonalności JPA - interfejs JPA w ramach Jakarta EE
      • dodanie pliku persistence.xml

        Plik konfiguracyjny persistence.xml - [listing dokumentu] [link do dokumentu]

          <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        <persistence xmlns="https://jakarta.ee/xml/ns/persistence"
                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
                     version="3.0">
            <persistence-unit name="PU_Postgresql" transaction-type="RESOURCE_LOCAL">
                <non-jta-data-source>java:comp/env/jdbc/postgres</non-jta-data-source>
                <class>zti.model2.Person</class>
                <properties>
                    <property name="eclipselink.target-database"
                              value="org.eclipse.persistence.platform.database.PostgreSQLPlatform" />
                    <property name="eclipselink.logging.level" value="FINER" />
                    <property name="eclipselink.logging.logger" value="ServerLogger" />
                </properties>
            </persistence-unit>
        </persistence>  
      • konfiguracja jednostki trwałości wykorzystuję plik context.xml uruchomiony w poprzednim zadaniu.
    2. W ramach punktu wykorzystamy następujące klasy Javy:
      • klasa JPAResource.java opisująca żądania obsługiwane przez aplikację (GET, POST, PUT i DELETE) oraz obsługę bazy danych poprzez interfejs JPA,
      • klasa Person.java typu POJO obsługująca dane przechowywane w tabeli Person.
    3. Klasa opisująca żądania obsługiwane przez aplikację (GET, POST, PUT i DELETE) i obsługę interfejsu JPA - pakiet lab04.

      Klasa JPAResource.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab04;
      
      import java.util.List;
      import java.util.Map;
      
      import jakarta.persistence.EntityManager;
      import jakarta.persistence.EntityManagerFactory;
      import jakarta.persistence.EntityTransaction;
      import jakarta.persistence.Persistence;
      import jakarta.ws.rs.Consumes;
      import jakarta.ws.rs.DELETE;
      import jakarta.ws.rs.GET;
      import jakarta.ws.rs.POST;
      import jakarta.ws.rs.PUT;
      import jakarta.ws.rs.Path;
      import jakarta.ws.rs.PathParam;
      import jakarta.ws.rs.Produces;
      import jakarta.ws.rs.core.MediaType;
      
      import zti.model2.Person;
      
      @Path("/jpa/person")
      public class JPAResource {
      
          private EntityManagerFactory managerFactory;
          private EntityManager entityManager; // = managerFactory.createEntityManager();
          private EntityTransaction entityTransaction;
      
          public JPAResource() {
              managerFactory = Persistence.createEntityManagerFactory("PU_Postgresql");
              entityManager = managerFactory.createEntityManager();
              entityTransaction = entityManager.getTransaction();
      
          }
      
          @GET
          @Produces({MediaType.APPLICATION_JSON})
          public Person[] get() {
              System.out.println("GET");
              List<Person> people = null;
              try {
                  @SuppressWarnings("unchecked")
                  List<Person> resultList = (List<Person>) entityManager.createNamedQuery("findAll").getResultList();
                  //people = (List<Person>) entityManager.createNamedQuery("findAll").getResultList();
                  people = resultList;
                  // manager.close();
              } catch (Exception e) {
                  System.out.println("Failed !!! " + e.getMessage());
              }
              return people.toArray(new Person[0]);
          }
      
      
          @GET
          @Path("{id}")
          @Produces({MediaType.APPLICATION_JSON})
          public Person get(@PathParam("id") String id) {
              System.out.println("GET");
              Person entity = (Person) entityManager.find(Person.class, Integer.parseInt(id));
              return entity;
          }
      
          @POST
          @Consumes({MediaType.APPLICATION_JSON})
          @Produces({MediaType.TEXT_PLAIN})
          public String post(Person person) {
              System.out.println("POST");
              entityTransaction.begin();
              entityManager.persist(person);
              entityManager.flush();
              entityTransaction.commit();
              return "add record" ;
          }
      
          @PUT
          @Consumes({MediaType.APPLICATION_JSON})
          @Produces({MediaType.TEXT_PLAIN})
          public String put(Person person) {
              System.out.println("PUT");
              entityTransaction.begin();
              entityManager.merge(person);
              entityTransaction.commit();
              return "update record" ;
          }
      
      
          @DELETE
          @Path("{id}")
          @Produces({MediaType.TEXT_PLAIN})
          public String delete(@PathParam("id") String id) {
              System.out.println("DELETE");
              entityTransaction.begin();
              Person entity = (Person) entityManager.find(Person.class, Integer.parseInt(id));
              entityManager.remove(entity);
              entityManager.flush();
              entityTransaction.commit();
              return "delete record" ;
          }
      
      }
      
      
      
    4. Klasa opisująca encję Person dla danych z tabeli PERSON w bazie danych - pakiet model2.

      Klasa Person.java - ( [listing dokumentu] [link do dokumentu]

      package zti.model2;
      
      import java.io.Serializable;
      import jakarta.persistence.*;
      
      
      /**
       * The persistent class for the person database table.
       *
       */
      
      @Entity (name="Person")
      @Table (name="person", schema="public")
      @NamedQuery(name="findAll", query="SELECT p FROM Person p ORDER BY p.lname")
      public class Person implements Serializable {
          private static final long serialVersionUID = 1L;
          private Integer id;
          private String city;
          private String email;
          private String fname;
          private String lname;
          private String tel;
      
          public Person() {
          }
      
      
          @Id
          //@SequenceGenerator(name="PERSON_ID_GENERATOR" )
          //@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PERSON_ID_GENERATOR")
          @GeneratedValue(strategy=GenerationType.IDENTITY)
          public Integer getId() {
              return this.id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
      
          public String getCity() {
              return this.city;
          }
      
          public void setCity(String city) {
              this.city = city;
          }
      
      
          public String getEmail() {
              return this.email;
          }
      
          public void setEmail(String email) {
              this.email = email;
          }
      
      
          public String getFname() {
              return this.fname;
          }
      
          public void setFname(String fname) {
              this.fname = fname;
          }
      
      
          public String getLname() {
              return this.lname;
          }
      
          public void setLname(String lname) {
              this.lname = lname;
          }
      
      
          public String getTel() {
              return this.tel;
          }
      
          public void setTel(String tel) {
              this.tel = tel;
          }
      
      }
      
    5. Na koniec dodajemy obsługę opracowanej funckjonalnośći w klasie Lab04Application.
          public Set<Class<?>> getClasses() {
              HashSet<Class<?>> set = new HashSet<Class<?>>();
              set.add(HelloResource.class);
              set.add(JDBCResource.class);
      		set.add(JPAResource.class);
              return set;
          }
      
    6. Na rys. 7 przedstawiono przykładową odpowiedź serwisu.
      Lab04_jpa
      Rys.7 Przykładowa odpowiedź serwisu z wykorzystanym interfejsem JPA.
  5. RESTful, klient JAX-RS

    1. W ramach tego zadania opracujemy klienta RESTful do opracowanego serwisu w ramach technologii JEE JAX-RS.
    2. Do prezentacji możliwości klienta JAX-RS wykorzystamy poniższy servlet - pakiet servlets.

      Servlet ClientREST.java - ( [listing dokumentu] [link do dokumentu]

      package zti.servlets;
      
      import java.io.IOException;
      import jakarta.servlet.ServletException;
      import jakarta.servlet.annotation.WebServlet;
      import jakarta.servlet.http.HttpServlet;
      import jakarta.servlet.http.HttpServletRequest;
      import jakarta.servlet.http.HttpServletResponse;
      import jakarta.ws.rs.client.Client;
      import jakarta.ws.rs.client.ClientBuilder;
      import jakarta.ws.rs.client.WebTarget;
      import jakarta.ws.rs.core.Response;
      
      /**
       * Servlet implementation class ClientREST
       */
      @WebServlet("/ClientREST")
      public class ClientREST extends HttpServlet {
          private static final long serialVersionUID = 1L;
      
          /**
           * @see HttpServlet#HttpServlet()
           */
          public ClientREST() {
              super();
              // TODO Auto-generated constructor stub
          }
      
          /**
           * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
           */
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // TODO Auto-generated method stub
              //response.getWriter().append("Served at: ").append(request.getContextPath());
              Client client = ClientBuilder.newClient();
              String url = "http://localhost:<port>/<context>/api/jpa/person/";
              resp.setContentType("application/json");
              Response res ;
              // WebTarget target;
              if (req.getParameterMap().containsKey("id")) {
                  //String id = req.getParameterValues("id")[0] ;
                  String id = req.getParameter("id");
                  res = client.target(url).path("/{id}").resolveTemplate("id", id).request("application/json").get();
              } else {
                  res = client.target(url).request("application/json").get();
              }
              resp.getWriter().append(res.readEntity(String.class));
          }
      
          /**
           * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
           */
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              // TODO Auto-generated method stub
              doGet(request, response);
          }
      
      }
      
    3. Na rys.8 przedstawiono przykładową odpowiedź klienta REST.
      Lab04_client_REST
      Rys.8 Przykładowa klienta zgodnego z JEE JAX-RS.
  6. CORS, Cross-Origin Resource Sharing

    1. Obsługę serwera zrealizowanego zgodnie z RESTful można obsługiwać aplikacją wykorzystującą język JavaScript i obiekt XMLHTTPRequest (XHR) lub funkcje fetch(). Jednak pojawia się tu trudność. Jeżeli aplikacja nie została pobrana z strony serwisu RESTful, to serwer odrzuci nasze żądanie zrealizowane z wykorzystaniem obiektu XHR - z języka JavaScript. Do poprawnego działania naszego serwisu wykorzystującego AJAX wymagane jest wdrożenie technologii CORS (Cross-Origin Resource Sharing). Wymaga to dodania do naszej aplikacji odpowiedniej klasy lub filtrów. Na początek testujemy prostą aplikację z funkcją fetch() do pobrania rekordów z naszego serwisu REST.
    2. Plik z funkcjonalnością XHR Rest.html - ( [listing dokumentu] [link do dokumentu]

      <!DOCTYPE html>
      <html>
         <head>
            <meta charset="utf-8">
            <title>Ajax: Serwis RESTful</title>
            <script type="text/javascript">
      	  var url = "http://localhost:<port>/<context>/api/jpa/person";
            // Lista rekordow w bazie
            function _list() {    
              fetch( url ) 
               .then ( response => { 
                 response.json().then ( data => {
                    var txt = "" ;
                    for ( let i=0; i<data.length ; i++)
                      txt += data[i].fname + " " + data[i].lname + "<br/>" ;
      			  document.getElementById('data').innerHTML = '';
                    document.getElementById('result').innerHTML = txt ;
                  }) ;		 
               })
            }	  
            function _ins_form() { }
            function _del_list() { }
            function _upd_list() { }	  
      	  </script>
         </head>
         <body>
           <div style="text-align:center" >
             <table border="1" bgcolor="gray">
               <tr><th><big>Test serwisu REST JAX-RS. Operacje CRUD.</big></th></tr>
             </table>
             <br />
             <form action="#">
                <table><tr>
                <td><input type="button" value="Pobranie danych z bazy" onclick="_list()"/></td>
                <td><input type="button" value="Dodanie rekordu do bazy" onclick="_ins_form()"/></td>
                <td><input type="button" value="Usuniecie rekordu z bazy" onclick="_del_list()"/></td>
                <td><input type="button" value="Poprawa rekordu w bazie" onclick="_upd_list()"/></td>
                </tr></table>
             </form>
           </div>
           <div id="data"></div>
           <div id="result"></div>
          </body>
      </html>
    3. W ramach opracowanej aplikacji dodamy klasę CorsFilter.java, która umożliwia poprawną obsługę żądań z języka JavaScript. Klasa umożliwia poprawne funkcjonowanie aplikacji uruchamianych z dowolnego adresu domenowego.

      Klasa CorsFilter.java - ( [listing dokumentu] [link do dokumentu]

      package zti.lab04;
      
      import jakarta.ws.rs.container.ContainerRequestContext;
      import jakarta.ws.rs.container.ContainerResponseContext;
      import jakarta.ws.rs.container.ContainerResponseFilter;
      import jakarta.ws.rs.ext.Provider;
      
      import java.io.IOException;
      
      @Provider
      public class CorsFilter implements ContainerResponseFilter {
      
          @Override
          public void filter(ContainerRequestContext requestContext,
                             ContainerResponseContext responseContext) throws IOException {
              responseContext.getHeaders().add(
                      "Access-Control-Allow-Origin", "*");
              responseContext.getHeaders().add(
                      "Access-Control-Allow-Credentials", "true");
              responseContext.getHeaders().add(
                      "Access-Control-Allow-Headers",
                      "origin, content-type, accept, authorization");
              responseContext.getHeaders().add(
                      "Access-Control-Allow-Methods",
                      "GET, POST, PUT, DELETE, OPTIONS, HEAD");
      
          }
      }
    4. Poprawne działanie powyższej klasy wymaga jeszcze zarejestrowania w klasie Lab04Application. obsługująca funkcjonalność CORS.
          public Set<Class<?>> getClasses() {
              HashSet<Class<?>> set = new HashSet<Class<?>>();
              set.add(HelloResource.class);
              set.add(JDBCResource.class);
      		set.add(JPAResource.class);
      		set.add(CorsFilter.class);
              return set;
          }
      

  7. Log - logowanie żądań i odpowiedzi

    1. W tym punkcie dodamy do opracowanej aplikacji klasę LogFilter.java, która umożliwi logowanie żądań i odpowiedzi.

      Klasa LogFilter.java - ( [listing dokumentu] [link do dokumentu]

      package zti.util;
      
      import jakarta.ws.rs.container.ContainerRequestContext;
      import jakarta.ws.rs.container.ContainerRequestFilter;
      import jakarta.ws.rs.container.ContainerResponseContext;
      import jakarta.ws.rs.container.ContainerResponseFilter;
      import jakarta.ws.rs.core.MultivaluedMap;
      import jakarta.ws.rs.core.UriInfo;
      import jakarta.ws.rs.ext.Provider;
      
      import java.io.IOException;
      
      @Provider
      //@Logged
      public class LogFilter implements ContainerRequestFilter, ContainerResponseFilter {
      
          @Override
          public void filter(ContainerRequestContext reqContext) throws IOException {
              System.out.println("-- req info --");
              UriInfo uriInfo = reqContext.getUriInfo();
              log(uriInfo, reqContext.getHeaders());
          }
      
          @Override
          public void filter(ContainerRequestContext reqContext,
                             ContainerResponseContext resContext) throws IOException {
              System.out.println("-- res info --");
              UriInfo uriInfo = reqContext.getUriInfo();
              log(uriInfo, resContext.getHeaders());
          }
      
          private void log(UriInfo uriInfo, MultivaluedMap<String, ?> headers) {
              System.out.println("Path: " + uriInfo.getPath());
              headers.entrySet().forEach(h -> System.out.println(h.getKey() + ": " + h.getValue()));
          }
      }
      
    2. Poprawne działanie powyższej klasy wymaga jeszcze zarejestrowania jej w klasie Lab04Application.
          public Set<Class<?>> getClasses() {
              HashSet<Class<?>> set = new HashSet<Class<?>>();
              set.add(HelloResource.class);
              set.add(JDBCResource.class);
      		set.add(JPAResource.class);
      		set.add(CorsFilter.class);
      		set.add(LogFilter.class);		
              return set;
          }
      

  8. Interfejs OpenAPI i aplikacji Swagger UI

    1. Ostatnim elementem, który dodamy do opracowanego projektu, będzie dodanie obsługi funkcjonalności OpenAPI ( opis na stronie https://www.openapis.org/ ) i interfejsu Swagger ( opis na stronie https://swagger.io ). Realizacja tego zadania wymaga dodania do naszej aplikacji kilku elementów, które umożliwią poprawną realizację postawionego zadania. Na początek dodamy dodatkowe pakiety, które są wymagane do realizacji projektu. Następnie dodamy plik (jeżeli nie istnieje) web.xml z odpowiednim wpisem. Aplikację Swagger-UI w wersji dist wstawimy do katalogu webapp oraz na koniec dodamy plik openapi.json.

    2. Wymagane pakiety do realizacji zadania. - [listing dokumentu] [link do dokumentu]

        <dependency>
          <groupId>io.swagger.core.v3</groupId>
          <artifactId>swagger-annotations</artifactId>
          <version>2.2.22</version>
      </dependency>
      <dependency>
          <groupId>io.swagger.core.v3</groupId>
          <artifactId>swagger-jaxrs2-jakarta</artifactId>
          <version>2.2.22</version>
      </dependency>
      <dependency>
          <groupId>io.swagger.core.v3</groupId>
          <artifactId>swagger-jaxrs2-servlet-initializer-jakarta</artifactId>
          <version>2.2.22</version>
      </dependency>  
    3. W katalogu webapp/WEB-INF w pliku web.xml umieszczamy wpis umożliwiający wywołanie odpowiednich funkcjonalności realizujących zadania OpenAPI.

      Plik web.xml po modyfikacji - [listing dokumentu] [link do dokumentu]

        <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
               version="5.0">
      
          <servlet>
              <servlet-name>OpenApi</servlet-name>
              <servlet-class>io.swagger.v3.jaxrs2.integration.OpenApiServlet</servlet-class>
              <init-param>
                  <param-name>openApi.configuration.resourcePackages</param-name>
                  <param-value>zti.zti_lab04</param-value>
              </init-param>
          </servlet>
          <servlet-mapping>
              <servlet-name>OpenApi</servlet-name>
              <url-pattern>/openapi/*</url-pattern>
          </servlet-mapping>
      
      </web-app>  
    4. W kolejnym punkcie umieścimy w katalogu webapp/swagger/dist aplikację obsługującej funkcjonalność aplikacji Swagger.

    5. Do poprawego działania aplikacji Swagger wymagana jest modyfikacja adresu pobierającego strukturę aplikacji w pliku swagger-initializer.js. Poniżej przykład modyfikacji w wymienionym skrypcie.

      Przykładowa modyfikacja skryptu swagger-initializer.js - [listing dokumentu] [link do dokumentu]

        window.onload = function() {
        //<editor-fold desc="Changeable Configuration Block">
      
        // the following lines will be replaced by docker/configurator, when it runs in a docker-container
        window.ui = SwaggerUIBundle({
          url: "http://localhost:8087/zti_lab04/openapi/swagger.json",
          dom_id: '#swagger-ui',
          deepLinking: true,
          presets: [
            SwaggerUIBundle.presets.apis,
            SwaggerUIStandalonePreset
          ],
          plugins: [
            SwaggerUIBundle.plugins.DownloadUrl
          ],
          layout: "StandaloneLayout"
        });
      
        //</editor-fold>
      };  
    6. Do poprawnego działania aplikacji Swagger wymagane jest dodanie pliku openapi.json zawierającego dodatkowe parametry. Plik umieścimy w katalogu main/resources w którym należy poprawnie zdefiniować url w atrybucie servers.

      Przykładowa zawartość pliku openapi.json - [listing dokumentu] [link do dokumentu]

        {
        "openAPI": {
          "info": {
            "version": "1.0",
            "title": "zti_lab",
            "description": "Swagger API: <b>ZTI Labs</b>",
            "termsOfService": "http://localhost:8087/",
            "contact": {
              "email": "contact@localhost",
              "name": "localhost",
              "url": "http://localhost:8087/"
            },
            "license": {
              "name": "localhost license",
              "url": "http://localhost:8087/"
            }
          },
          "servers": [
            {
              "url": "http://localhost:8087/zti_lab04/lab",
              "description": "Development server"
            }
          ]
        }
      }  
    7. Poprawne wykonanie zadania umożliwi otrzymanie ekranu przedstawionego na rys.9.
      Lab04_OpenAPI_Swagger
      Rys.9 Uruchomiona funkcjonalność OpenAPI z wykorzystaniem aplikacji Swagger.

Zaliczenie laboratorium

  1. Przesłanie zrealizowanych w trakcie zajęć przykładowych skryptów w pliku *.war (z kodem źródłowym skryptów)
  2. Przesłanie klienta realizującego wszystkie funkcje CRUD przedstawionych wyżej serwisów RESTful w jednej z poniższych technologii:
    • strona WWW z wykorzystaniem języka HTML i Javascript z obiektem XHR lub funkcji fetch() lub odpowiedniego frameworku,
    • zrealizowanie klienta z wykorzystaniem servletów (można wykorzystać JSF)
  3. Na rys.10 przedstawiono przykładową aplikację wykorzystującą XHR.
    Lab04_RESTful_fetch
    Rys.10 Przykładowa aplikacja w HTML wykorzystująca funkcję fetch().