Commit 623a5154 by Patryk Czarnik

Oddzielna klasa dla formatu XML

parent 47fe6bf7
REST - Representative State Transfer
Bezpośrednie wykorzystanie protokołu HTTP w celu:
zdalnego dostępu do zasobów (czyli do danych)
realizacji usług biznesowych
Charakterystyczne cechy usług REST:
zasoby są umieszczone pod adresami URL
struktura adresów odpowiada logicznej strukturze danych
operacje HTTP: GET PUT DELETE realizują swoją semantykę odczyt / zapis / usunięcie
przy czym to twórca usługi decyduje, które operacje są dostępne
zasoby mogą być odczytywane i zapisywane (czyli „transferowane”) w różnych formatach. Najbardziej popularny format dla danych obiektowych to JSON, ale często także XML, a w zależności od potrzeb można używać innych (np. obrazki, PDF-y)
ten sam zasób (z tego samego adresu) może być dostępny w różnych formatach
W zaawansowanych usługach REST-owych duża rolę odgrywają metadane, przede wszystkim odnośniki prowadzące z jednego zasobu do innego zasobu, podobnie jak klucze obce w SQL.
Wg niektórych źródeł istnieją trzy poziomy zaawansowania usług REST:
0. Podstawowy interfejs programistyczny oparty o HTTP, np. pobieranie danych z określonego adresu. Bez struktury, bez konsekwentnego wykorzystania operacji HTTP.
1. Podstawowa usługa REST - mniej więcej taka, jaką wykonujemy na zajęciach
2. Usługa z dużą ilością metadanych dodawanych do JSONa, wykorzystująca np. dodatkowe znaczniki ATOM lub HATEOAS. W Springu taką usługę może zrealizować za pomocą bibliotek jak REST Repositories lub HATEOAS.
W Javie serwer REST można realizować za pomocą:
JAX-RS, część Jakarta EE
Trzy implementacje:
Jersey - ref. impl., używana na serwerach Glassfish oraz WebLogic
RestEasy - Redhat - używana przez serwer WildFly
Apache CXF - programista może użyć jako biblioteki np. na serwerze Tomcat
Spring MVC (czyli kontrolerów Springa)
Dodatkowych bibliotek Spring REST Repositories, Spring HATEOAS... - większa automatyzacja
Moduł Jersey w aplikacji Spring Boot - użycie technologii JAX-RS w ramach Springa
...@@ -26,6 +26,11 @@ ...@@ -26,6 +26,11 @@
<version>42.6.0</version> <version>42.6.0</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -18,10 +18,16 @@ import sklep.db.RecordNotFound; ...@@ -18,10 +18,16 @@ import sklep.db.RecordNotFound;
import sklep.model.Product; import sklep.model.Product;
@Path("/products.json") @Path("/products.json")
@Produces("application/json")
@Consumes("application/json")
// Adnotacje @Produces / @Consumes na poziomie klasy mówią co domyślnie produkują i konsumują metody.
// - metoda może nadpisać te ustawienia (np. metody dot. zdjęć)
// - adnotacje dotyczą tylko tych metod, które faktycznie coś pobierają lub zwracają
// Np. metoda, która niczego nie konsumuje, nie zwróci uwagi na te adnotacje z poziomu klasy.
// Metoda typu void nie zwraca uwagi na adnotację Produces
public class RProductsJSON { public class RProductsJSON {
@GET @GET
@Produces("application/json")
public List<Product> readAll() throws DBException { public List<Product> readAll() throws DBException {
try(DBConnection db = DBConnection.open()) { try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO(); ProductDAO productDAO = db.productDAO();
...@@ -31,7 +37,6 @@ public class RProductsJSON { ...@@ -31,7 +37,6 @@ public class RProductsJSON {
@GET @GET
@Path("/{id}") @Path("/{id}")
@Produces("application/json")
public Product readOne(@PathParam("id") int productId) throws DBException, RecordNotFound { public Product readOne(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) { try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO(); ProductDAO productDAO = db.productDAO();
...@@ -40,7 +45,6 @@ public class RProductsJSON { ...@@ -40,7 +45,6 @@ public class RProductsJSON {
} }
@POST @POST
@Consumes("application/json")
// W metodach typu POST i PUT powinien znajdować się dokładnie jeden parametr nieozanczony żadną adnotacją. // W metodach typu POST i PUT powinien znajdować się dokładnie jeden parametr nieozanczony żadną adnotacją.
// Do tego parametru zostanie przekazana wartość utworzona na podstawie treści zapytania (content / body / entity). // Do tego parametru zostanie przekazana wartość utworzona na podstawie treści zapytania (content / body / entity).
// W adnotacji @Consumes określamy format, w jakim te dane mają być przysłane. // W adnotacji @Consumes określamy format, w jakim te dane mają być przysłane.
...@@ -59,7 +63,6 @@ public class RProductsJSON { ...@@ -59,7 +63,6 @@ public class RProductsJSON {
// Właściwą strukturą adresu będzie wtedy np. products/3/price // Właściwą strukturą adresu będzie wtedy np. products/3/price
@GET @GET
@Path("/{id}/price") @Path("/{id}/price")
@Produces("application/json")
public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound { public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) { try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO(); ProductDAO productDAO = db.productDAO();
...@@ -70,7 +73,6 @@ public class RProductsJSON { ...@@ -70,7 +73,6 @@ public class RProductsJSON {
// Metoda PUT służy w HTTP do zapisywania danych DOKŁADNIE POD PODANYM ADRESEM // Metoda PUT służy w HTTP do zapisywania danych DOKŁADNIE POD PODANYM ADRESEM
@PUT @PUT
@Path("/{id}/price") @Path("/{id}/price")
@Consumes("application/json")
public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound { public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) { try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO(); ProductDAO productDAO = db.productDAO();
......
package rest;
import java.math.BigDecimal;
import java.util.List;
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 sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
@Path("/products.xml")
@Produces("application/xml")
@Consumes("application/xml")
public class RProductsXML {
@GET
public List<Product> readAll() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.readAll();
}
}
@GET
@Path("/{id}")
public Product readOne(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
@POST
public void saveProduct(Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
productDAO.save(product);
db.commit();
}
}
@GET
@Path("/{id}/price")
public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId).getPrice();
}
}
@PUT
@Path("/{id}/price")
public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
product.setPrice(newPrice);
productDAO.update(product);
db.commit();
}
}
@DELETE
@Path("/{id}")
public void delete(@PathParam("id") int productId) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
productDAO.delete(productId);
db.commit();
}
}
@GET
@Path("/{id}/photo")
@Produces("image/jpeg")
public byte[] getPhoto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
}
...@@ -2,6 +2,9 @@ package sklep.model; ...@@ -2,6 +2,9 @@ package sklep.model;
import java.util.Objects; import java.util.Objects;
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Customer { public class Customer {
private String email; private String email;
private String name; private String name;
......
package sklep.model;
import java.time.LocalDateTime;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
public class DateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
@Override
public String marshal(LocalDateTime dt) throws Exception {
return dt.toString();
}
@Override
public LocalDateTime unmarshal(String s) throws Exception {
return LocalDateTime.parse(s);
}
}
...@@ -7,15 +7,29 @@ import java.util.Collections; ...@@ -7,15 +7,29 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement
public class Order { public class Order {
@XmlAttribute(name="id")
private Integer orderId; private Integer orderId;
@XmlElement(name="customer-email")
private String customerEmail; private String customerEmail;
@XmlElement(name="order-date")
@XmlJavaTypeAdapter(DateTimeAdapter.class)
private LocalDateTime orderDate; private LocalDateTime orderDate;
@XmlAttribute(name="status")
private Status orderStatus; private Status orderStatus;
@XmlElementWrapper(name="products")
@XmlElement(name="product")
public final List<OrderProduct> products = new ArrayList<>(); public final List<OrderProduct> products = new ArrayList<>();
public Order() { public Order() {
......
...@@ -3,13 +3,24 @@ package sklep.model; ...@@ -3,13 +3,24 @@ package sklep.model;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Objects; import java.util.Objects;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlTransient;
public class OrderProduct { public class OrderProduct {
// Ponieważ te obiekty w XML są zawsze umieszczone wewnątrz konkretnego zamówienia,
// to nie ma sensu umieszczać informacji o id tego zamówienia.
// Uwaga - tak można sobie upraszczać, gdy mówimy o odczycie danych (komunikacja jendokierunkowa).
@XmlTransient
// @XmlAttribute(name="order-id")
private Integer orderId; private Integer orderId;
@XmlAttribute(name="product-id")
private Integer productId; private Integer productId;
private int quantity; private int quantity;
@XmlElement(name="price")
private BigDecimal actualPrice; private BigDecimal actualPrice;
public OrderProduct() { public OrderProduct() {
......
...@@ -3,9 +3,16 @@ package sklep.model; ...@@ -3,9 +3,16 @@ package sklep.model;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Objects; import java.util.Objects;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Product { public class Product {
@XmlAttribute(name="id")
private Integer productId; private Integer productId;
@XmlElement(name="product-name")
private String productName; private String productName;
private BigDecimal price; private BigDecimal price;
......
@XmlAccessorType(XmlAccessType.FIELD)
package sklep.model;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment