Commit b752bbcf by Patryk Czarnik

Dodatkowe projekty od Patryka ;)

parent e5b1e235
/target/
/.settings/
/.classpath
/.project
/*.iml
/.idea/
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.alx.kjava</groupId>
<artifactId>PC24-SklepWeb</artifactId>
<version>1.0-SNAPSHOT</version>
<name>PC24-SklepWeb</name>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
<!-- jbc Maven pozwala też podawać zakresy wersji, ale wtedy mamy 'niestabilne buildy'
<version>[3.0.0, 4)</version>-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
DROP TABLE IF EXISTS order_products;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
DROP TABLE IF EXISTS products;
DROP SEQUENCE IF EXISTS products_seq;
DROP SEQUENCE IF EXISTS orders_seq;
DROP TYPE IF EXISTS order_status;
CREATE SEQUENCE products_seq START 10;
CREATE SEQUENCE orders_seq START 10;
CREATE TABLE products
(
product_id INTEGER DEFAULT nextval('products_seq'),
product_name VARCHAR(100) NOT NULL,
-- 10 cyfr, z czego 2 po prawej stronie kropki
-- zakres wartości od -99999999.99 do +99999999.99
-- inna nazwa: DECIMAL, w Oraclu nazwa NUMBER
price NUMERIC(10, 2) NOT NULL,
vat NUMERIC(2, 2), -- wartości do 0.99
description TEXT, -- w Oraclu pod nazwą CLOB
PRIMARY KEY (product_id),
CHECK (price > 0),
CHECK (vat >= 0),
CHECK (length(product_name) > 0)
);
-- Nie zawsze kluczem głównym jest automatycznie generowany numer.
-- Kluczem może być też kolumna tekstowa, albo typu "data z czasem" (timestamp).
-- Przykładowo tutaj napiszemy, że kluczem klienta jest jego email.
CREATE TABLE customers
(
customer_email VARCHAR(100),
customer_name VARCHAR(100) NOT NULL,
phone_number VARCHAR(20),
address VARCHAR(250),
postal_code VARCHAR(10),
city VARCHAR(100),
PRIMARY KEY (customer_email)
);
CREATE TYPE order_status AS ENUM (
'NEW',
'CONFIRMED',
'PAID',
'SHIPPED',
'DELIVERED',
'CLOSED',
'RETURNED'
);
CREATE TABLE orders
(
order_id INTEGER DEFAULT nextval('orders_seq'),
customer_email VARCHAR(100) NOT NULL,
status order_status DEFAULT 'NEW' NOT NULL,
order_date TIMESTAMP DEFAULT current_timestamp NOT NULL,
delivery_date DATE,
PRIMARY KEY (order_id),
FOREIGN KEY (customer_email) REFERENCES customers (customer_email),
-- kolumna customer_email z tej tabeli wskazuje na kolumnę customer_email z tabeli customers
-- (można wpisać tylko email istniejącego klienta)
CHECK (delivery_date >= order_date::DATE)
);
CREATE TABLE order_products
(
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity SMALLINT DEFAULT 1 NOT NULL,
actual_price NUMERIC(10, 2) NOT NULL,
actual_vat NUMERIC(2, 2),
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders (order_id),
FOREIGN KEY (product_id) REFERENCES products (product_id)
);
INSERT INTO products(product_id, product_name, price, vat, description)
VALUES (1, 'pralka', 2900.00, 0.23, 'Pralka szybkoobrotowa');
INSERT INTO products(product_id, product_name, price, vat, description)
VALUES (2, 'odkurzacz', 800.00, 0.23, 'Odkurzacz automatyczny');
INSERT INTO products(product_id, product_name, price, vat, description)
VALUES (3, 'telewizor 55"', 3300.00, 0.23, 'Telewizor 55 cali 4K');
INSERT INTO products(product_id, product_name, price, vat, description)
VALUES (4, 'telewizor 40"', 2200.00, 0.23, 'Telewizor 40 Full HD');
INSERT INTO products(product_id, product_name, price, vat)
VALUES (5, 'myszka gejmerska', 444.00, 0.23);
INSERT INTO customers(customer_email, phone_number, customer_name, address, postal_code, city)
VALUES ('ala@example.com', '123123123', 'Ala Kowalska', 'Jasna 14/16', '01-234', 'Warszawa');
INSERT INTO customers(customer_email, phone_number, customer_name, address, postal_code, city)
VALUES ('ola@example.com', '321321321', 'Ola Malinowska', 'Ciemna 133', '99-999', 'Pcim');
INSERT INTO orders(order_id, customer_email, order_date, status)
VALUES (1, 'ala@example.com', '2021-11-20 12:30:00', 'PAID');
INSERT INTO orders(order_id, customer_email, order_date, status, delivery_date)
VALUES (2, 'ola@example.com', '2021-11-18 10:00:00', 'SHIPPED', '2021-12-01');
INSERT INTO orders(order_id, customer_email)
VALUES (3, 'ala@example.com');
INSERT INTO order_products(order_id, product_id, quantity, actual_price, actual_vat)
VALUES (1, 1, 1, 2900.00, 0.23);
INSERT INTO order_products(order_id, product_id, quantity, actual_price, actual_vat)
VALUES (1, 2, 3, 2400.00, 0.23);
INSERT INTO order_products(order_id, product_id, quantity, actual_price, actual_vat)
VALUES (2, 2, 1, 800.00, 0.23);
INSERT INTO order_products(order_id, product_id, quantity, actual_price, actual_vat)
VALUES (3, 4, 1, 2200.00, 0.23);
INSERT INTO order_products(order_id, product_id, quantity, actual_price, actual_vat)
VALUES (3, 3, 1, 300.00, 0.23);
INSERT INTO order_products(order_id, product_id, quantity, actual_price, actual_vat)
VALUES (3, 5, 1, 1000.00, 0.23);
-- SELECT * FROM products;
-- SELECT * FROM orders FULL JOIN customers USING(customer_email);
-- SELECT * FROM orders LEFT JOIN customers USING(customer_email) LEFT JOIN order_products USING(order_id) LEFT JOIN products USING(product_id) ORDER BY order_id, product_id;
package sklep.basket;
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.servlet.http.HttpSession;
import sklep.db.DBConnection;
import sklep.db.ProductDAO;
import sklep.model.Product;
@WebServlet("/add_to_basket")
public class AddToBasket extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
int productId = Integer.parseInt(request.getParameter("productId"));
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
HttpSession sesja = request.getSession();
Basket basket = (Basket)sesja.getAttribute("basket");
// Zakładamy, że obiekt basket został dodany do sesji przez BasketListener
basket.addProduct(product);
}
} catch(Exception e) {
// ignorujemy błędy
}
// Przekierowanie - każemy przeglądarce wejść pod ten adres.
response.sendRedirect("products9.jsp");
}
}
package sklep.basket;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import sklep.model.Product;
public class Basket {
private final Map<Integer, ProductInBasket> elementy = new HashMap<>();
public synchronized void addProduct(Product product, int quantity) {
if(elementy.containsKey(product.getProductId())) {
// jeśli w słowniku jest już taki element, to tylko zwiększamy ilość
elementy.get(product.getProductId()).increaseQuantity(quantity);
} else {
// jeśli jeszcze nie ma, to tworzymy
elementy.put(product.getProductId(),
new ProductInBasket(product.getProductId(), product.getProductName(), product.getPrice(), quantity));
}
}
public synchronized void addProduct(Product product) {
// "domyślną ilością, o którą zwiększamy, jest 1"
addProduct(product, 1);
}
public synchronized void removeProduct(int productId) {
ProductInBasket p = elementy.get(productId);
if(p != null) {
if(p.getQuantity() > 1) {
p.increaseQuantity(-1);
} else {
elementy.remove(productId);
}
}
}
public synchronized Collection<ProductInBasket> getElements() {
return Collections.unmodifiableCollection(elementy.values());
}
public synchronized BigDecimal getTotalValue() {
return getElements().stream()
.map(ProductInBasket::getValue)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public synchronized String toString() {
return "Koszyk o rozmiarze " + getElements().size()
+ " i wartości " + getTotalValue();
}
}
package sklep.basket;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
@WebListener
public class BasketListener implements HttpSessionListener, ServletContextListener {
public void sessionCreated(HttpSessionEvent se) {
HttpSession sesja = se.getSession();
sesja.setMaxInactiveInterval(30); // pół minuty i sesja wygasa
System.out.println("sessionCreated " + sesja.getId());
Basket basket = new Basket();
sesja.setAttribute("basket", basket);
}
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession sesja = se.getSession();
System.out.println("sessionDestroyed " + sesja.getId());
}
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized");
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed");
}
}
package sklep.basket;
import java.math.BigDecimal;
import java.util.Objects;
public class ProductInBasket {
private int productId;
private String productName;
private BigDecimal price;
private int quantity;
public ProductInBasket(int productId, String productName, BigDecimal price, int quantity) {
this.productId = productId;
this.productName = productName;
this.price = price;
this.quantity = quantity;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public int getProductId() {
return productId;
}
public String getProductName() {
return productName;
}
@Override
public String toString() {
return "ElementKoszyka [productId=" + productId + ", productName=" + productName + ", price=" + price
+ ", quantity=" + quantity + "]";
}
@Override
public int hashCode() {
return Objects.hash(price, productId, productName, quantity);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ProductInBasket other = (ProductInBasket) obj;
return Objects.equals(price, other.price) && productId == other.productId
&& Objects.equals(productName, other.productName) && quantity == other.quantity;
}
public BigDecimal getValue() {
return price.multiply(BigDecimal.valueOf(quantity));
}
public void increaseQuantity(int change) {
quantity += change;
}
}
package sklep.basket;
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.servlet.http.HttpSession;
@WebServlet("/remove_from_basket")
public class RemoveFromBasket extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
int productId = Integer.parseInt(request.getParameter("productId"));
HttpSession sesja = request.getSession();
Basket basket = (Basket) sesja.getAttribute("basket");
basket.removeProduct(productId);
} catch (Exception e) {
// ignorujemy błędy
}
// Przekierowanie - każemy przeglądarce wejść pod ten adres.
response.sendRedirect("products9.jsp");
}
}
package sklep.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import sklep.model.Customer;
public class CustomerDAO {
private final DBConnection db;
CustomerDAO(DBConnection db) {
this.db = db;
}
public Customer findByEmail(String email) throws DBException, RecordNotFound {
final String sql = "SELECT * FROM customers WHERE customer_email = ?";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, email);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return customerFromRS(rs);
} else {
throw new RecordNotFound("Cannot find customer with email " + email);
}
}
} catch (SQLException e) {
throw new DBException("SQL error in CustomerDAO.findById: " + e.getMessage(), e);
}
}
public List<Customer> readAll() throws DBException {
final String sql = "SELECT * FROM customers";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
return customerListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in CustomerDAO.readAll: " + e.getMessage(), e);
}
}
private List<Customer> customerListFromRS(ResultSet rs) throws SQLException {
List<Customer> records = new ArrayList<>();
while (rs.next()) {
Customer product = customerFromRS(rs);
records.add(product);
}
return records;
}
private Customer customerFromRS(ResultSet rs) throws SQLException {
return new Customer(
rs.getString("customer_email"),
rs.getString("customer_name"),
rs.getString("phone_number"),
rs.getString("address"),
rs.getString("postal_code"),
rs.getString("city"));
}
public void insert(Customer customer) throws DBException {
// używać gdy obiekt ma wpisane ID (tu: email)
final String sql = "INSERT INTO customers("
+ "customer_email, customer_name, phone_number, address, postal_code, city)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, customer.getEmail());
stmt.setString(2, customer.getName());
stmt.setString(3, customer.getPhoneNumber());
stmt.setString(4, customer.getAddress());
stmt.setString(5, customer.getPostalCode());
stmt.setString(6, customer.getCity());
stmt.executeUpdate();
} catch (SQLException e) {
throw new DBException("Error during INSERT CUSTOMER", e);
}
}
public boolean update(Customer customer) throws DBException {
final String sql = "UPDATE customers SET "
+ " customer_name=?, phone_number=?, address=?, postal_code=?, city=?"
+ " WHERE customer_email = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, customer.getName());
stmt.setString(2, customer.getPhoneNumber());
stmt.setString(3, customer.getAddress());
stmt.setString(4, customer.getPostalCode());
stmt.setString(5, customer.getCity());
stmt.setString(6, customer.getEmail());
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during UPDATE CUSTOMER", e);
}
}
public void save(Customer customer) throws DBException {
if(customer.getEmail() == null) {
throw new IllegalArgumentException("Customer email cannot be null");
} else {
if(! update(customer)) {
insert(customer);
}
}
}
public boolean delete(String email) throws DBException {
final String sql = "DELETE FROM customers WHERE customer_email = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, email);
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during DELETE CUSTOMER", e);
}
}
public boolean delete(Customer customer) throws DBException {
return delete(customer.getEmail());
}
}
package sklep.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBConnection implements AutoCloseable {
private Connection sqlConnection;
private DBConnection(Connection sqlConnection) {
this.sqlConnection = sqlConnection;
}
public static DBConnection open() throws DBException {
return open(false);
}
public static DBConnection open(boolean autoCommit) throws DBException {
try {
Properties props = DBSettings.load();
if(props.containsKey("driver_class")) {
Class.forName(props.getProperty("driver_class"));
}
Connection c = DriverManager.getConnection(props.getProperty("url") , props);
c.setAutoCommit(autoCommit);
return new DBConnection(c);
} catch (ClassNotFoundException | SQLException e) {
throw new DBException("Cannot connect to postgresql: " + e, e);
}
}
public static DBConnection openLocalhost() throws DBException {
try {
Connection c = DriverManager.getConnection("jdbc:postgresql://localhost/sklep", "alx", "abc123");
c.setAutoCommit(false);
return new DBConnection(c);
} catch (SQLException e) {
throw new DBException("Cannot connect to postgresql: " + e, e);
}
}
@Override
public void close() {
try {
if (sqlConnection != null) {
sqlConnection.close();
sqlConnection = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getSqlConnection() {
return sqlConnection;
}
public void commit() throws DBException {
try {
sqlConnection.commit();
} catch (SQLException e) {
throw new DBException("Error during commit: " + e.getMessage(), e);
}
}
public void rollback() throws DBException {
try {
sqlConnection.rollback();
} catch (SQLException e) {
throw new DBException("Error during rollback: " + e.getMessage(), e);
}
}
public ProductDAO productDAO() {
return new ProductDAO(this);
}
public CustomerDAO customerDAO() {
return new CustomerDAO(this);
}
public OrderDAO orderDAO() {
return new OrderDAO(this);
}
}
package sklep.db;
public class DBException extends SklepException {
public DBException() {
super();
}
public DBException(String message, Throwable cause) {
super(message, cause);
}
public DBException(String message) {
super(message);
}
}
package sklep.db;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class DBSettings {
public static final String DB_SETTINGS_SYSTEM_PROPERTY = "sklep.db_settings_location";
private static final String INTERNAL_DEFAULT_PROPERTIES = "/sklep.properties";
private static DBSettings dbSettings; // singleton
private final Properties props;
private DBSettings() throws DBException {
props = new Properties();
String systemProperty = System.getProperty(DB_SETTINGS_SYSTEM_PROPERTY);
try(InputStream input = systemProperty != null
? new FileInputStream(new File(systemProperty))
: DBSettings.class.getResourceAsStream(INTERNAL_DEFAULT_PROPERTIES) ) {
props.load(input);
} catch (IOException e) {
//e.printStackTrace();
throw new DBException("Cannot read settings. " + e, e);
}
}
public static synchronized DBSettings getInstance() throws DBException {
// Dla klasy typu "singleton" w aplikacji powstaje tylko jedna instancja (obiekt) tej klasy.
// Dostęp do tego obiektu odbywa się poprzez metodę statyczną taką jak ta.
// Tutaj mamy "leniwą inicjalizację", czyli obiekt jest tworzony przy pierwszej próbie dostepu.
if(dbSettings == null) {
dbSettings = new DBSettings();
}
return dbSettings;
}
public Properties getProperties() {
return props;
}
public static Properties load() throws DBException {
return getInstance().getProperties();
}
}
package sklep.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import sklep.model.Order;
import sklep.model.OrderProduct;
public class OrderDAO {
private static final String[] ID_COLUMNS = {"order_id", "order_date"};
private final DBConnection db;
OrderDAO(DBConnection db) {
this.db = db;
}
public Order findById(int orderId) throws DBException, RecordNotFound {
final String sql = "SELECT * FROM orders WHERE order_id = ?";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, orderId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return orderFromRS(rs);
} else {
throw new RecordNotFound("Cannot find order with id " + orderId);
}
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.findById: " + e.getMessage(), e);
}
}
public List<Order> readAll() throws DBException {
final String sql = "SELECT * FROM orders ORDER BY order_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
return orderListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.readAll: " + e.getMessage(), e);
}
}
public List<Order> customerOrders(String email) throws DBException {
final String sql = "SELECT * FROM orders WHERE customer_email = ? ORDER BY order_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, email);
try (ResultSet rs = stmt.executeQuery()) {
return orderListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.customerOrders: " + e.getMessage(), e);
}
}
private List<Order> orderListFromRS(ResultSet rs) throws SQLException, DBException {
List<Order> orders = new ArrayList<>();
while (rs.next()) {
Order order = orderFromRS(rs);
orders.add(order);
}
return orders;
}
private Order orderFromRS(ResultSet rs) throws SQLException, DBException {
Order order = Order.ofDbFields(
rs.getInt("order_id"),
rs.getString("customer_email"),
rs.getTimestamp("order_date"),
rs.getString("status"));
order.addProducts(orderProductsForOrder(order.getOrderId()));
return order;
}
List<OrderProduct> orderProductsForOrder(int orderId) throws DBException {
final String sql = "SELECT * FROM order_products WHERE order_id = ? ORDER BY product_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, orderId);
try (ResultSet rs = stmt.executeQuery()) {
List<OrderProduct> ops = new ArrayList<>();
while(rs.next()) {
ops.add(orderProductFromRS(rs));
}
return ops;
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.customerOrders: " + e.getMessage(), e);
}
}
private OrderProduct orderProductFromRS(ResultSet rs) throws SQLException {
return new OrderProduct(rs.getInt("order_id"), rs.getInt("product_id"), rs.getInt("quantity"), rs.getBigDecimal("actual_price"));
}
}
package sklep.db;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import sklep.model.Product;
/* DAO - Data Access Object(s)
Dostęp do baz danych oparty o takie zasady:
tabelom bazodanowym odpowiadają klasy w naszej aplikacji
np. dla tabeli products mamy klasę Product (w pakiecie model)
dla takie pary tabela products + klasa Product tworzymy klasę narzędziową ProductDAO , której zadaniem jest obsługa tej tabeli: odczyt, zapis, wyszukiwanie, i inne operacje jeśli są potrzebne.
*/
public class ProductDAO {
private static final BigDecimal MAX_PRICE = new BigDecimal(1_000_000_000);
private static final String[] ID_COLUMNS = {"product_id"};
private final DBConnection db;
ProductDAO(DBConnection db) {
this.db = db;
}
public Product findById(int productId) throws DBException, RecordNotFound {
final String sql = "SELECT * FROM products WHERE product_id = ?";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, productId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return productFromRS(rs);
} else {
throw new RecordNotFound("Cannot find product with id " + productId);
}
}
} catch (SQLException e) {
throw new DBException("SQL error in ProductDAO.findById: " + e.getMessage(), e);
}
}
public List<Product> readAll() throws DBException {
final String sql = "SELECT * FROM products ORDER BY product_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
return productListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in ProductDAO.readAll: " + e.getMessage(), e);
}
}
public List<Product> findByPrice(BigDecimal minPrice, BigDecimal maxPrice) throws DBException {
final String sql = "SELECT * FROM products WHERE price BETWEEN ? AND ? ORDER BY product_id";
if(minPrice == null)
minPrice = BigDecimal.ZERO;
if(maxPrice == null)
maxPrice = MAX_PRICE;
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setBigDecimal(1, minPrice);
stmt.setBigDecimal(2, maxPrice);
try (ResultSet rs = stmt.executeQuery()) {
return productListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in ProductDAO.findByPrice: " + e.getMessage(), e);
}
}
private List<Product> productListFromRS(ResultSet rs) throws SQLException {
List<Product> products = new ArrayList<>();
while (rs.next()) {
Product product = productFromRS(rs);
products.add(product);
}
return products;
}
private Product productFromRS(ResultSet rs) throws SQLException {
return new Product(
rs.getInt("product_id"),
rs.getString("product_name"),
rs.getBigDecimal("price"),
rs.getBigDecimal("vat"),
rs.getString("description"));
}
public void insert(Product product) throws DBException {
// używać gdy obiekt ma wpisane ID
final String sql = "INSERT INTO products("
+ " product_id, product_name, price, vat, description)"
+ " VALUES (?, ?, ?, ?, ?)";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, product.getProductId());
stmt.setString(2, product.getProductName());
stmt.setBigDecimal(3, product.getPrice());
stmt.setBigDecimal(4, product.getVat());
stmt.setString(5, product.getDescription());
stmt.executeUpdate();
} catch (SQLException e) {
throw new DBException("Error during INSERT PRODUCT", e);
}
}
public void insertNew(Product product) throws DBException {
// używać gdy obiekt nie ma wpisanego ID (productID == null)
final String sql = "INSERT INTO products("
+ " product_name, price, vat, description)"
+ " VALUES (?, ?, ?, ?)";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql, ID_COLUMNS)) {
stmt.setString(1, product.getProductName());
stmt.setBigDecimal(2, product.getPrice());
stmt.setBigDecimal(3, product.getVat());
stmt.setString(4, product.getDescription());
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if(rs.next()) {
// w obiekcie, który mamy w pamięci, uzupełniamy brakujące ID na podstawie tego, co wygenerowała baza
product.setProductId(rs.getInt(1));
}
};
} catch (SQLException e) {
throw new DBException("Error during INSERT PRODUCT", e);
}
}
public boolean update(Product product) throws DBException {
final String sql = "UPDATE products SET "
+ " product_name = ?, price = ?, vat = ?, description = ?"
+ " WHERE product_id = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, product.getProductName());
stmt.setBigDecimal(2, product.getPrice());
stmt.setBigDecimal(3, product.getVat());
stmt.setString(4, product.getDescription());
stmt.setInt(5, product.getProductId());
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during UPDATE PRODUCT", e);
}
}
public void save(Product product) throws DBException {
if(product.getProductId() == null) {
// wstawiamy nowy rekord korzystajac z sekwecji
insertNew(product);
} else {
if(! update(product)) {
insert(product);
}
}
}
public boolean delete(int productId) throws DBException {
final String sql = "DELETE FROM products WHERE product_id = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, productId);
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during DELETE PRODUCT", e);
}
}
public boolean delete(Product product) throws DBException {
return delete(product.getProductId());
}
}
package sklep.db;
public class RecordNotFound extends SklepException {
public RecordNotFound() {
super();
}
public RecordNotFound(String message) {
super(message);
}
}
package sklep.db;
public class SklepException extends Exception {
public SklepException() {
super();
}
public SklepException(String message, Throwable cause) {
super(message, cause);
}
public SklepException(String message) {
super(message);
}
public SklepException(Throwable cause) {
super(cause);
}
}
package sklep.db;
import java.util.List;
import sklep.model.Product;
public class ZwyklyOdczyt_DAO {
public static void main(String[] args) {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
List<Product> products = productDAO.readAll();
for (Product product : products) {
System.out.println(product);
}
} catch (DBException e) {
e.printStackTrace();
}
}
}
package sklep.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ZwyklyOdczyt_JDBC {
public static void main(String[] args) {
String url = "jdbc:postgresql://localhost:5432/sklep";
String sql = "SELECT * FROM products";
try(Connection c = DriverManager.getConnection(url, "alx", "abc123");
PreparedStatement stmt= c.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while(rs.next()) {
System.out.printf("%d: %s za cenę %s\n",
rs.getInt("product_id"), rs.getString("product_name"), rs.getBigDecimal("price"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
package sklep.model;
import java.util.Objects;
public class Customer {
private String email;
private String name;
private String phoneNumber;
private String address;
private String postalCode;
private String city;
public Customer() {
}
public Customer(String email, String name, String phone, String address, String postalCode, String city) {
this.email = email;
this.name = name;
this.phoneNumber = phone;
this.address = address;
this.postalCode = postalCode;
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phone) {
this.phoneNumber = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Customer [email=" + email + ", name=" + name + ", phone=" + phoneNumber + ", address=" + address
+ ", postalCode=" + postalCode + ", city=" + city + "]";
}
@Override
public int hashCode() {
return Objects.hash(email, name, address, city, phoneNumber, postalCode);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
return Objects.equals(email, other.email) && Objects.equals(name, other.name)
&& Objects.equals(address, other.address) && Objects.equals(city, other.city)
&& Objects.equals(phoneNumber, other.phoneNumber) && Objects.equals(postalCode, other.postalCode);
}
}
package sklep.model;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class Order {
private Integer orderId;
private String customerEmail;
private LocalDateTime orderDate;
private Status orderStatus;
public final List<OrderProduct> products = new ArrayList<>();
public Order() {
}
public Order(Integer orderId, String customerEmail, LocalDateTime orderDate, Status orderStatus) {
this.orderId = orderId;
this.customerEmail = customerEmail;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
}
public static Order ofDbFields(int orderId, String customerEmail, java.sql.Timestamp orderDate, String orderStatus) {
return new Order(orderId, customerEmail,
orderDate.toLocalDateTime(),
Status.valueOf(orderStatus.toUpperCase()));
}
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public String getCustomerEmail() {
return customerEmail;
}
public void setCustomerEmail(String customerEmail) {
this.customerEmail = customerEmail;
}
public LocalDateTime getOrderDate() {
return orderDate;
}
public void setOrderDate(LocalDateTime orderDate) {
this.orderDate = orderDate;
}
public Status getOrderStatus() {
return orderStatus;
}
public void setOrderStatus(Status orderStatus) {
this.orderStatus = orderStatus;
}
public List<OrderProduct> getProducts() {
return Collections.unmodifiableList(products);
}
public void addProduct(OrderProduct product) {
this.products.add(product);
}
public void addProducts(Collection<OrderProduct> products) {
this.products.addAll(products);
}
public void setProducts(Collection<OrderProduct> products) {
this.products.clear();
this.products.addAll(products);
}
@Override
public String toString() {
return "Order [orderId=" + orderId + ", customerEmail=" + customerEmail + ", orderDate=" + orderDate
+ ", orderStatus=" + orderStatus + "]";
}
@Override
public int hashCode() {
return Objects.hash(customerEmail, orderDate, orderId, orderStatus);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Order other = (Order) obj;
return Objects.equals(customerEmail, other.customerEmail) && Objects.equals(orderDate, other.orderDate)
&& Objects.equals(orderId, other.orderId) && orderStatus == other.orderStatus;
}
public enum Status {
NEW,
CONFIRMED,
PAID,
SHIPPED,
CLOSED,
RETURNED,
;
}
}
package sklep.model;
import java.math.BigDecimal;
import java.util.Objects;
public class OrderProduct {
private Integer orderId;
private Integer productId;
private int quantity;
private BigDecimal actualPrice;
public OrderProduct() {
}
public OrderProduct(Integer orderId, Integer productId, int quantity, BigDecimal actualPrice) {
this.orderId = orderId;
this.productId = productId;
this.quantity = quantity;
this.actualPrice = actualPrice;
}
public static OrderProduct of(Integer orderId, Product product, int quantity) {
return new OrderProduct(orderId, product.getProductId(), quantity, product.getPrice());
}
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public BigDecimal getActualPrice() {
return actualPrice;
}
public void setActualPrice(BigDecimal actualPrice) {
this.actualPrice = actualPrice;
}
@Override
public String toString() {
return "OrderProduct [orderId=" + orderId + ", productId=" + productId + ", quantity=" + quantity
+ ", actualPrice=" + actualPrice+ "]";
}
@Override
public int hashCode() {
return Objects.hash(orderId, productId, quantity, actualPrice);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrderProduct other = (OrderProduct) obj;
return Objects.equals(orderId, other.orderId) && Objects.equals(productId, other.productId)
&& quantity == other.quantity
&& Objects.equals(actualPrice, other.actualPrice);
}
}
package sklep.model;
import java.math.BigDecimal;
import java.util.Objects;
public class Product {
private Integer productId;
private String productName;
private BigDecimal price;
private BigDecimal vat;
private String description;
public Product() {
}
public Product(Integer productId, String productName, BigDecimal price, BigDecimal vat, String description) {
this.productId = productId;
this.productName = productName;
this.price = price;
this.vat = vat;
this.description = description;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getVat() {
return vat;
}
public void setVat(BigDecimal vat) {
this.vat = vat;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int hashCode() {
return Objects.hash(description, price, vat, productId, productName);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Product other = (Product) obj;
return Objects.equals(productId, other.productId) && Objects.equals(productName, other.productName)
&& Objects.equals(price, other.price)
&& Objects.equals(vat, other.vat)
&& Objects.equals(description, other.description);
}
@Override
public String toString() {
return "Product [productId=" + productId + ", productName=" + productName + ", price=" + price + ", vat=" + vat
+ ", description=" + description + "]";
}
public String toHtml() {
return String.format("<div class='product'>"
+ "<h2>%s</h2>"
+ "<p>(nr %d)</p>"
+ "<p>Cena: <strong>%,.2f PLN</strong></p>"
+ "<p>%s</p>"
+ "</div>",
getProductName(),
getProductId(),
getPrice(),
getDescription());
}
}
package sklep.photo;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import sklep.db.RecordNotFound;
@WebServlet("/photo")
public class Photo extends HttpServlet {
/* Ten serwlet wczytuje z dysku plik ze zdjęciem o podanym numerze (z parametru productId).
* Aby odesłać odpowiedź "binarną" (a nie tekstową) używamy getOutputStream() zamiast getWriter().
* Aby przeglądarka wiedziała, że otrzymuje grafikę, ustawiamy content-type image/jpeg.
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String parametrId = request.getParameter("productId");
if(parametrId == null) {
return;
}
try {
int id = Integer.parseInt(parametrId);
byte[] bytes = PhotoUtil.readBytes(id);
response.setContentType("image/jpeg");
ServletOutputStream output = response.getOutputStream();
output.write(bytes);
output.close();
} catch (RecordNotFound e) {
response.setStatus(404);
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.getWriter().println("Nie ma zdjęcia dla produktu o nr " + parametrId);
} catch (Exception e) {
response.setStatus(500);
e.printStackTrace();
}
}
}
package sklep.photo;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
// Włączona obsługa zapytań multipart ("z załącznikami"). Maks rozmiar zapytania/pliku: 16M
@WebServlet("/photo_upload")
@MultipartConfig(maxRequestSize = 16 * 1024 * 1024)
public class PhotoUpload extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String paramId = request.getParameter("productId");
if(paramId != null) {
int productId = Integer.parseInt(paramId);
Part part = request.getPart("plik");
if(part != null) {
// przysłano plik
// Tutaj nazwa pliku jest dla nas bez znaczenia, ale gdybyśmy potrzebowali, to w ten sposób:
// String nazwaPliku = part.getSubmittedFileName();
// Przypisujemy bajty ze strumienia do pliku w katalogu ze zdjęciami:
PhotoUtil.writeStream(productId, part.getInputStream());
}
}
} catch (Exception e) {
// wypisujemy błąd, ale metoda kończy się normalnie
e.printStackTrace();
}
response.sendRedirect("products9.jsp");
}
}
package sklep.photo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import sklep.db.DBException;
import sklep.db.DBSettings;
import sklep.db.RecordNotFound;
public class PhotoUtil {
private static final String EXT = ".jpg";
public static File getFile(int productId) throws DBException, RecordNotFound {
Path path = getPath(productId);
File file = path.toFile();
if(file.exists()) {
return file;
} else {
throw new RecordNotFound("Cannot read photo for product id = " + productId);
}
}
public static byte[] readBytes(int productId) throws DBException, RecordNotFound {
Path path = getPath(productId);
try {
return Files.readAllBytes(path);
} catch (IOException e) {
// System.err.println(e);
throw new RecordNotFound("Cannot read photo for product id = " + productId);
}
}
public static void writeStream(int productId, InputStream inputStream) {
try {
Path path = getPath(productId);
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
// wypisujemy błąd, ale metoda kończy się normalnie
e.printStackTrace();
}
}
private static Path getPath(int productId) throws DBException {
String dir = DBSettings.load().getProperty("photo_dir");
String fileName = productId + EXT;
return Paths.get(dir, fileName);
}
}
package sklep.web;
import java.io.IOException;
import java.math.BigDecimal;
import jakarta.servlet.RequestDispatcher;
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 sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
@WebServlet("/edit")
public class EditProduct extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String parametrId = request.getParameter("productId");
if(parametrId != null) {
int productId = Integer.parseInt(parametrId);
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
// Gdy do obiektu request dodamy atrybut, to stanie się on dostępny dla kolejnych komponentów
// naszej aplikacji, które będą obsługiwać to zapytanie.
// W tym przypadku skrypt JSP może odwoływać się do obiektu product.
// Obiekt request jest też nośnikiem danych, podobnie jak sesja i servletContext.
// To działa jak Model w Spring MVC.
// Tylko jeśli znajdę produkt, tylko wtedy dodaję go do requestu i JSP wyświetli jego dane.
// Jeśli parametru productId nie było lub produktu nie znaleziono, to wyświetli się pusty formularz.
request.setAttribute("product", product);
} catch(DBException | RecordNotFound e) {
e.printStackTrace();
}
}
// Forward to "wewnętrzne przekierowanie" obsługi zapytania do innego komponentu aplikacji.
// Tutaj "wyświetlamy" formularz edycji produktu.
RequestDispatcher dispatcher = request.getRequestDispatcher("product_form.jsp");
if(dispatcher != null)
dispatcher.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// W tej wersji nie obsługujemy błędów - w razie błędu wyświetli się strona z wyjątkiem
// W przypadku braku ID zostanie utworzony nowy produkt, a w przypadku podania ID (gdy to była edycja istniejącego) - zostanie zastąpiony.
request.setCharacterEncoding("UTF-8");
String parametrId = request.getParameter("productId");
Integer productId = (parametrId == null || parametrId.isEmpty()) ? null : Integer.valueOf(parametrId);
String parametrPrice = request.getParameter("price");
BigDecimal price = new BigDecimal(parametrPrice);
String parametrVat = request.getParameter("vat");
BigDecimal vat = (parametrVat == null || parametrVat.isEmpty()) ? null : new BigDecimal(parametrVat);
String name = request.getParameter("productName");
String description = request.getParameter("description");
Product product = new Product(productId, name, price, vat, description);
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
productDAO.save(product);
db.commit();
// Gdy udało się zapisać, to przejdziemy z powrotem do listy.
// To jest przekierowanie przeglądarki do inny adres.
response.sendRedirect("products9.jsp");
} catch (DBException e) {
e.printStackTrace();
}
}
}
package sklep.web;
import java.math.BigDecimal;
import java.util.List;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.model.Product;
/* Ta klasa jest po to, aby w skrypcie JSP w wygodny sposób odczytać sobie listę produktów z bazy danych. */
public class ProductBean {
private BigDecimal minPrice, maxPrice;
// Chociaż wewnętrznie zmienna jest typu BigDecimal, to gettery i settery napiszemy tak, jakby to były Stringi.
// Robimy to po to, aby w JSP zadziałało setProperty.
public String getMinPrice() {
return minPrice == null ? null : minPrice.toString();
}
public void setMinPrice(String minPrice) {
if(minPrice == null || minPrice.isEmpty()) {
this.minPrice = null;
} else {
this.minPrice = new BigDecimal(minPrice);
}
}
public String getMaxPrice() {
return maxPrice == null ? null : maxPrice.toString();
}
public void setMaxPrice(String maxPrice) {
if(maxPrice == null || maxPrice.isEmpty()) {
this.maxPrice = null;
} else {
this.maxPrice = new BigDecimal(maxPrice);
}
}
// Metoda wygląda jak getter, ale wewnętrznie czyta dane z bazy, a nie z własnej zmiennej.
public List<Product> getAllProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.readAll();
}
}
// Metoda odczytuje produkty zgodnie z ustawionymi wcześniej kryteriami (w tym przykładzie są to ceny, ale może być więcej filtrów).
public List<Product> getFilteredProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
return db.productDAO().findByPrice(minPrice, maxPrice);
}
}
}
package sklep.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/products0")
public class Products0 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("Zaraz odczytam produkty z bazy...");
final String url = "jdbc:postgresql://localhost/sklep";
final String sql = "SELECT * FROM products";
try(Connection c = DriverManager.getConnection(url , "alx", "abc123");
Statement stmt = c.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while(rs.next()) {
out.printf("Produkt nr %s to jest %s za cenę %s\n",
rs.getInt("product_id"), rs.getString("product_name"), rs.getBigDecimal("price"));
}
} catch(SQLException e) {
out.println("Wielka bieda!");
e.printStackTrace(out);
}
}
}
package sklep.web;
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 sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.model.Product;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet("/products1")
public class Products1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("Zaraz odczytam produkty z bazy...");
try(DBConnection c = DBConnection.open()) {
ProductDAO productDAO = c.productDAO();
List<Product> products = productDAO.readAll();
for(Product p : products) {
out.printf("Produkt nr %d to jest %s za cenę %s\n",
p.getProductId(), p.getProductName(), p.getPrice());
}
} catch (DBException e) {
out.println("Bład: " + e);
e.printStackTrace(out);
}
}
}
package sklep.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
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 sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.model.Product;
@WebServlet("/products2")
public class Products2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("""
<!DOCTYPE html>
<html>
<head>
<title>Lista produktów</title>
<link rel='stylesheet' type='text/css' href='styl.css'>
</head>
<body>
<h1>Produkty</h1>
""");
try (DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
List<Product> products = productDAO.readAll();
for (Product product : products) {
out.println(product.toHtml());
}
} catch (DBException e) {
out.println("Wielka bieda!");
out.print("<pre>");
e.printStackTrace(out);
out.print("</pre>");
}
out.println("</body></html>");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_4_0.xsd">
</beans>
\ No newline at end of file
<?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="default">
</persistence-unit>
</persistence>
url=jdbc:postgresql://localhost:5432/sklep
driver_class=org.postgresql.Driver
user=alx
password=abc123
photo_dir=/home/patryk/sklep/foto
# Sciezki na Windows: albo piszemy slashe / , albo podwojne backslashe \\
# photo_dir=C:/Users/Patryk/Desktop/sklep/foto
# photo_dir=C:\\Users\\Patryk\\Desktop\\sklep\\foto
<?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_6_0.xsd"
version="6.0">
</web-app>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sklep - spis treści</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Sklep Web</h1>
<h2>Lista produktów w różnych wersjach</h2>
<h3>Wersje niedoskonałe, nie do naśladowania.</h3>
<ul>
<li><a href="products0">wersja 0</a> - samodzielny prosty serwlet</li>
<li><a href="products1">wersja 1</a> - serwlet oparty o klasy DAO</li>
<li><a href="products2">wersja 2</a> - serwlet oparty o klasy DAO z HTMLem</li>
<li><a href="products3.jsp">wersja 3</a> - JSP ze skryptletami</li>
<li><a href="products4.jsp">wersja 4</a> - JSP z tagami SQL</li>
</ul>
<h3>Wersje przyzwoite ;-)</h3>
<ul>
<li><a href="products5a.jsp">wersja 5</a> - JSP oparty o klasę bean</li>
<li><a href="products6.jsp">wersja 6</a> - wyszukiwanie wg ceny - bean</li>
<li><a href="products7.jsp">wersja 7</a> - edycja produktów</li>
<li><a href="products8.jsp">wersja 8</a> - koszyk</li>
<li><a href="products9.jsp">wersja 9</a> - fotki</li>
</ul>
<h3>Dodatkowe strony</h3>
<ul>
<li><a href="photo?productId=2">Foto</a> - przykładowe zdjęcie</li>
<li><a href="photo_upload.jsp?productId=2">Zmień zdjęcie</a> nr 2</li>
<li><a href="edit_product?productId=1">Edytuj produkt nr 1</a></li>
<li><a href="edit_product">Dodaj nowy produkt</a></li>
</ul>
</body>
</html>
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Edycja zdjęcia</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Wgraj zdjęcie produktu</h1>
<div>Produkt nr <strong>${param.productId}</strong></div>
<div>Aktualne zdjęcie:<br/>
<img class="photo" src="photo?productId=${param.productId}" alt="Brak zdjęcia">
</div>
<%-- action powoduje, że zapytanie z formularza jest wysyłane pod podany adres, a nie bieżący.
Aby wysłać zawartość pliku (a nie tylko jego nazwę), należy ustawić enctype jak poniżej.
Sam plik to pole formularza typu file; oprócz niego mogą być inne zwykłe pola.
Odpowiednio trzeba to też obsłużyć w serwlecie - patrz klasa PhotoUpload.
--%>
<form id="photo-form" method="post" action="photo_upload" enctype="multipart/form-data">
<input type="hidden" name="productId" value="${param.productId}">
<label for="plik">Wybierz plik ze zdjęciem</label>
<input id="plik" type="file" name="plik" accept="image/jpeg">
<br>
<button>Wyślij</button>
</form>
<p>[<a href="products9.jsp">powrót do listy produktów</a>]</p>
<p>[<a href="index.html">powrót do spisu treści</a>]</p>
</body>
</html>
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Edycja danych produktu</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Edycja produktu</h1>
<form id="product-form" method="post">
<table class="form">
<tr>
<td><label for="productId">Numer:</label></td>
<td><input id="productId" name="productId" placeholder="brak" type="number" readonly="readonly" value="${product.productId}"/></td>
</tr>
<tr>
<td><label for="productName">Nazwa towaru:</label></td>
<td><input id="productName" name="productName" placeholder="nazwa..." type="text" value="${product.productName}"/>
</td>
</tr>
<tr>
<td><label for="price">Cena:</label></td>
<td><input id="price" name="price" placeholder="12.90" title="tu wpisz cenę" type="number" step="0.01" value="${product.price}"/>
</td>
</tr>
<tr>
<td><label for="vat">Stawka VAT:</label></td>
<td><input id="vat" name="vat" placeholder="0.23" title="tu wpisz vat" type="number" step="0.01" value="${product.vat}"/>
</td>
</tr>
<tr>
<td><label for="description">Opis:</label></td>
<td><textarea id="description" name="description" rows="10" cols="120">${product.description}</textarea></td>
</tr>
<tr>
<td><button>Zapisz</button></td>
</tr>
</table>
</form>
<div class="action"><a href="products9.jsp">powrót do listy produktów</a></div>
<div class="action"><a href="index.html">powrót do spisu treści</a></div>
</body>
</html>
<%@page import="sklep.model.Product"%>
<%@page import="java.util.List"%>
<%@page import="sklep.db.ProductDAO"%>
<%@page import="sklep.db.DBConnection"%>
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 3</title>
</head>
<body>
<h1>Lista produktów - wersja 3 JSP</h1>
<p>W tej wersji wewnątrz skryptu JSP umieścimy fragmenty Javy, tzw. scriptlets.
To jest pierwsza techniczna możliwość, którą oferował standard JSP.</p>
<p>Uwaga - ta wersja nadal jest nieporządna i <strong>nie jest</strong> wzorem do naśladowania.
Mieszanie kodu HTML z Javą w taki sposób, szczególnie jak zrobiliśmy z pętlą pod koniec, jest w bardzo złym stylu.
</p>
<p>[<a href="index.html">powrót do spisu treści</a>]</p>
<%-- Poniżej skryptlet, który otwiera połączenie z bazą danych: --%>
<%
DBConnection db = DBConnection.open();
ProductDAO dao = db.productDAO();
%>
<p>Pobieranie danych...</p>
<%
List<Product> products = dao.readAll();
%>
<p>Pobrano <%= products.size() %> produktów. </p>
<h3>Wszystkie produkty</h3>
<ul>
<% for(Product product : products) { %>
<li><%= product.getProductName() %> za <%= product.getPrice() %></li>
<% } %>
</ul>
<% db.close(); %>
<p>[<a href="index.html">powrót do spisu treści</a>]</p>
</body>
</html>
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<%@taglib prefix="sql" uri="jakarta.tags.sql"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 4</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Lista produktów - wersja 4 JSP</h1>
<p>W tej wersji korzystamy ze standardowej biblioteki tagów (JSTL), a dokładnie z jej fragmentu obsługującego SQL.</p>
<p>Bezpośrednio w JSP wykonamy zapytanie w bazie danych - to też jeszcze nie będzie najładniejszy styl...</p>
<%-- W tej wersji za pomocą dedykowanych tagów JSP zadamy zapytanie SQL.
Ta wersja W OGÓLE nie używa klas stworzonych przez nas w projekcie; jest samowystarczalna.
--%>
<%-- "taglibs" - biblioteki tagów, zaimplementowane w Javie, a w JSP używa się ich za pomocą składni "tagowej" (dokładnie składni XML) --%>
<sql:setDataSource var="baza" driver="org.postgresql.Driver"
url="jdbc:postgresql://localhost/sklep"
user="alx" password="abc123"/>
<sql:query dataSource="${baza}" scope="page" var="result">
SELECT * FROM products ORDER BY product_id
</sql:query>
<%-- .product_name .price itp - to są nazwy kolumn w tabeli SQL --%>
<c:forEach var="row" items="${result.rows}">
<div class="product">
<h3>${row.product_name}</h3>
<div class="price">${row.price}</div>
<p>${row.description}</p>
</div>
</c:forEach>
</body>
</html>
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 5</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Lista produktów - wersja 5</h1>
<jsp:useBean id="bean" class="sklep.web.ProductBean"/>
<%-- tak jakby for(Product product : bean.getAllProducts()) --%>
<c:forEach var="product" items="${bean.allProducts}">
<div class="product">
<h3>${product.productName}</h3>
<div class="price">Cena: ${product.price}</div>
<div class="price">VAT ${product.vat * 100}%</div>
<c:if test="${not empty(product.description)}">
<p class="description">${product.description}</p>
</c:if>
</div>
</c:forEach>
</body>
</html>
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 5</title>
</head>
<body>
<h1>Lista produktów - wersja 5</h1>
<jsp:useBean id="bean" class="sklep.web.ProductBean"/>
<%-- tak jakby for(Product product : bean.getAllProducts()) --%>
<ul>
<c:forEach var="product" items="${bean.allProducts}">
<li>${product}</li>
</c:forEach>
</ul>
</body>
</html>
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 6</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Lista produktów - wersja 6</h1>
<form id="wyszukiwarka" method="get">
<h2>Filtr cen</h2>
<table class="formularz">
<tr><td><label for="min_price">Cena minimalna:</label></td>
<td><input id="min_price" name="min_price" type="number" value="${param.min_price}"></td></tr>
<tr><td><label for="max_price">Cena maksymalna:</label></td>
<td><input id="max_price" name="max_price" type="number" value="${param.max_price}"></td></tr>
<tr><td><button>Filtruj</button></td></tr>
</table>
</form>
<jsp:useBean id="bean" class="sklep.web.ProductBean"/>
<jsp:setProperty name="bean" property="minPrice" param="min_price"/>
<jsp:setProperty name="bean" property="maxPrice" param="max_price"/>
<c:forEach var="product" items="${bean.filteredProducts}">
<div class="product">
<h3>${product.productName}</h3>
<div class="price">Cena: ${product.price}</div>
<div class="price">VAT ${product.vat * 100}%</div>
<c:if test="${not empty(product.description)}">
<p class="description">${product.description}</p>
</c:if>
</div>
</c:forEach>
</body>
</html>
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 7</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Lista produktów - wersja 7</h1>
<form id="wyszukiwarka" method="get">
<h2>Filtr cen</h2>
<table class="formularz">
<tr><td><label for="min_price">Cena minimalna:</label></td>
<td><input id="min_price" name="min_price" type="number" value="${param.min_price}"></td></tr>
<tr><td><label for="max_price">Cena maksymalna:</label></td>
<td><input id="max_price" name="max_price" type="number" value="${param.max_price}"></td></tr>
<tr><td><button>Filtruj</button></td></tr>
</table>
</form>
<jsp:useBean id="bean" class="sklep.web.ProductBean"/>
<jsp:setProperty name="bean" property="minPrice" param="min_price"/>
<jsp:setProperty name="bean" property="maxPrice" param="max_price"/>
<c:forEach var="product" items="${bean.filteredProducts}">
<div class="product">
<h3>${product.productName}</h3>
<div class="price">Cena: ${product.price}</div>
<div class="price">VAT ${product.vat * 100}%</div>
<c:if test="${not empty(product.description)}">
<p class="description">${product.description}</p>
</c:if>
<div class="action"><a href="edit?productId=${product.productId}">edytuj</a></div>
</div>
</c:forEach>
<div class="action"><a href="edit">Dodaj nowy produkt</a></div>
</body>
</html>
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 8</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Lista produktów - wersja 8</h1>
<div class="koszyk">
<h4>Koszyk</h4>
<ul>
<%-- Zauważmy, że dla obiektu koszyk nie wykonujemy już useBean.
Po prostu zakładamy, że jest obecny (w sesji). Gdyby go nie było, to pętla się nie wykona. --%>
<c:forEach var="elm" items="${basket.elements}">
<li>${elm.productName} (${elm.quantity}) za <b>${elm.value}</b> <a href="remove_from_basket?productId=${elm.productId}">(–)</a></li>
</c:forEach>
</ul>
<p class="total">Do zapłaty: ${basket.totalValue}</p>
</div>
<form id="wyszukiwarka" method="get">
<h2>Filtr cen</h2>
<table class="formularz">
<tr><td><label for="min_price">Cena minimalna:</label></td>
<td><input id="min_price" name="min_price" type="number" value="${param.min_price}"></td></tr>
<tr><td><label for="max_price">Cena maksymalna:</label></td>
<td><input id="max_price" name="max_price" type="number" value="${param.max_price}"></td></tr>
<tr><td><button>Filtruj</button></td></tr>
</table>
</form>
<jsp:useBean id="bean" class="sklep.web.ProductBean"/>
<jsp:setProperty name="bean" property="minPrice" param="min_price"/>
<jsp:setProperty name="bean" property="maxPrice" param="max_price"/>
<c:forEach var="product" items="${bean.filteredProducts}">
<div class="product">
<h3>${product.productName}</h3>
<div class="price">Cena: ${product.price}</div>
<div class="price">VAT ${product.vat * 100}%</div>
<c:if test="${not empty(product.description)}">
<p class="description">${product.description}</p>
</c:if>
<div class="action"><a href="add_to_basket?productId=${product.productId}">dodaj do koszyka</a></div>
<div class="action"><a href="edit?productId=${product.productId}">edytuj</a></div>
</div>
</c:forEach>
<div class="action"><a href="edit">Dodaj nowy produkt</a></div>
</body>
</html>
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lista produktów 9</title>
<link rel="stylesheet" type="text/css" href="styl.css">
</head>
<body>
<h1>Lista produktów - wersja 9</h1>
<div class="koszyk">
<h4>Koszyk</h4>
<ul>
<%-- Zauważmy, że dla obiektu koszyk nie wykonujemy już useBean.
Po prostu zakładamy, że jest obecny (w sesji). Gdyby go nie było, to pętla się nie wykona. --%>
<c:forEach var="elm" items="${basket.elements}">
<li>${elm.productName} (${elm.quantity}) za <b>${elm.value}</b> <a href="remove_from_basket?productId=${elm.productId}">(–)</a></li>
</c:forEach>
</ul>
<p class="total">Do zapłaty: ${basket.totalValue}</p>
</div>
<form id="wyszukiwarka" method="get">
<h2>Filtr cen</h2>
<table class="formularz">
<tr><td><label for="min_price">Cena minimalna:</label></td>
<td><input id="min_price" name="min_price" type="number" value="${param.min_price}"></td></tr>
<tr><td><label for="max_price">Cena maksymalna:</label></td>
<td><input id="max_price" name="max_price" type="number" value="${param.max_price}"></td></tr>
<tr><td><button>Filtruj</button></td></tr>
</table>
</form>
<jsp:useBean id="bean" class="sklep.web.ProductBean"/>
<jsp:setProperty name="bean" property="minPrice" param="min_price"/>
<jsp:setProperty name="bean" property="maxPrice" param="max_price"/>
<c:forEach var="product" items="${bean.filteredProducts}">
<div class="product">
<img class="photo" src="photo?productId=${product.productId}" alt=""/>
<h3>${product.productName}</h3>
<div class="price">Cena: ${product.price}</div>
<div class="price">VAT ${product.vat * 100}%</div>
<c:if test="${not empty(product.description)}">
<p class="description">${product.description}</p>
</c:if>
<div class="action"><a href="add_to_basket?productId=${product.productId}">dodaj do koszyka</a></div>
<div class="action"><a href="edit?productId=${product.productId}">edytuj</a></div>
<div class="action"><a href="photo_upload.jsp?productId=${product.productId}">zmień zdjęcie</a></div>
</div>
</c:forEach>
<div class="action"><a href="edit">Dodaj nowy produkt</a></div>
</body>
</html>
body {
background-color: #FFFFDD;
font-family: 'Arial', sans-serif;
}
/* komentarz w CSS */
h4 {
text-align: center;
}
h2, h3, h4 {
margin-top: 0;
}
.product {
border: solid 2px blue;
margin: 1em auto 1em 50px;
padding: 1em;
background-color: white;
width: 800px;
min-height: 230px;
clear: right;
}
.koszyk {
position: fixed;
right: 0;
top: 0;
width: 300px;
height: 400px;
background-color: white;
border: outset 3px green;
}
#wyszukiwarka {
background-color: #AAEEFF;
width: 800px;
border: 2px black solid;
margin: 1em 400px 1em 50px;
padding: 1em;
}
.error {
background-color: #FFFFFF;
border: 6px double red;
margin: 20px;
padding: 10px;
color: red;
}
.photo {
display: block;
float: right;
max-width: 300px;
max-height: 200px;
margin: 5px;
}
.description {
font-size: smaller;
font-style: italic;
}
div.action {
font-size: smaller;
font-family: 'Arial', sans-serif;
font-weight: bold;
background-color: #DDDDDD;
border: 2px #444466 outset;
padding: 6px;
margin: 4px auto 4px 4px;
max-width: 200px;
}
.action:hover {
background-color: #EEEEEE;
border: 2px #4455CC outset;
}
.action:active {
background-color: #EEEEEE;
border: 2px #CC4455 inset;
}
.action a {
display: inline-block;
color: inherit;
text-decoration: none;
width: 100%;
}
.action a:hover {
color: #0000CC;
}
/target/
/.settings/
/.classpath
/.project
/*.iml
/.idea/
/mvnw
/mvnw.cmd
/.mvn/
JAX-RS (Jakarta API for Restful Services)
Realizacja usług typu REST (a także strony klienta) w ramach technologii Jakarta.
Jest częścią Java/Jakarta Enterprise Edition od Java EE 7.
Technologia oparta o adnotacje, inspirowana adnotacjami i sposobem działania Spring MVC, ale bardziej dostosowana do specyfiki usług REST.
Dostępne implementacje tej technologii:
- Jersey - używana przez serwery Glassfish i WebLogic; łatwo jej użyć także w ramach aplikacji Spring Boot - jest gotowa zależność w Spring Initializr,
- RestEasy - uzywana przez serwery z rodziny JBoss / WildFly
- Apache CXF
Każdej z nich da się także użyć jako biblioteki (zależności) w aplikacji uruchamianej w lekkim kontenerze serwletów, jak Tomcat czy Jetty.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.alx.kjava</groupId>
<artifactId>PC27-RestSerwer</artifactId>
<version>1.0-SNAPSHOT</version>
<name>PC27-RestSerwer</name>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>2.9</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package hello;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Path("/now")
@Produces("text/plain;charset=UTF-8")
// @Singleton
public class DataCzas {
// W JAX-RS domyślne zachowanie jest takie, że dla każdego requestu tworzony jest nowy obiekt klasy obsługującej zapytania.
// (polityka "per request"; można ją zmienić za pomocą adnotacji @Singleton - wtedy jeden obiekt obsłuży wszystkie zapytania)
// Daje to możliwość zapisania pewnych ulotnych informacji w polach prywatnych tej klasy - nikt nam ich nie nadpisze.
private LocalDateTime now = LocalDateTime.now();
{
// ten blok wykona się podczas tworzenia każdego obiektu
System.out.println("Powstał obiekt DataCzas z czasem równym " + now);
}
// ta metoda obsługuje adres .../api/now
@GET
public LocalDateTime odczytajDataICzas() {
return now;
}
// ta metoda obsługuje adres .../api/now/date
@GET
@Path("/date")
public LocalDate odczytajDate() {
return now.toLocalDate();
}
// ta metoda obsługuje adres .../api/now/date/year
@GET
@Path("/date/year")
public int odczytajRok() {
return now.getYear();
}
// ta metoda obsługuje adres .../api/now/date/month
@GET
@Path("/date/month")
public int odczytajMiesiac() {
return now.getMonthValue();
}
// ta metoda obsługuje adres .../api/now/date/day
@GET
@Path("/date/day")
public int odczytajDzien() {
return now.getDayOfMonth();
}
// ta metoda obsługuje adres .../api/now/time
@GET
@Path("/time")
public LocalTime odczytajCzas() {
return now.toLocalTime();
}
// ta metoda obsługuje adres .../api/now/time/second
@GET
@Path("/time/second")
public int odczytajSekunde() {
return now.getSecond();
}
}
package hello;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import java.util.Set;
// Ta klasa stanowi ogólną konfigurację usługi REST
// Musi dziedziczyć z klasy Application i posiadać adnotację @ApplicationPath
// W @ApplicationPath podajemy ogólny prefiks umieszczony przed wszystkimi adresami zasobów restowych.
// Oprócz tej klasy w projekcie może się znajdować wiele klas oznaczonych @Path - to są klasy zasobów
// Przykładowo jeśli w klasie mamy @Path("/products") a w @ApplicationPath jest adres "/api"
// to zapytania mają być wysyłane pod /ADRES-APLIKCAJI-NA-SERWRERZE/api/products
@ApplicationPath("/api")
public class HelloApplication extends Application {
}
\ No newline at end of file
package hello;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
@Path("/hello-world")
public class HelloResource {
@GET
@Produces("text/plain")
public String hello() {
return "Hello, World!";
}
}
\ No newline at end of file
package hello;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
@Path("/calc")
public class Kalkulator {
@GET
@Path("/{x}+{y}")
public long dodaj(@PathParam("x") long a, @PathParam("y") long b) {
return a+b;
}
@GET
@Path("/{x}-{y}")
public long odejmij(@PathParam("x") long a, @PathParam("y") long b) {
return a-b;
}
@GET
@Path("/{x}*{y}")
public long pomnoz(@PathParam("x") long a, @PathParam("y") long b) {
return a*b;
}
@GET
@Path("/{x}/{y}")
public long podziel(@PathParam("x") long a, @PathParam("y") long b) {
return a/b;
}
}
package hello;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response;
import java.time.LocalDateTime;
import java.util.Arrays;
@Path("/parametry")
@Produces("text/plain")
public class Parametry {
@Path("/witaj")
@GET
// w Springu byłoby tak: @GetMapping(path="/witaj", produces="text/plain")
public String witaj(@QueryParam("imie") String imie) {
return "Witaj " + imie;
}
// .../api/parametry/query?a=Ala&b=Basia&t=Tomek&t=Tadeusz&t=Tola
@GET
@Path("/query")
public String query(
@QueryParam("a") String a,
@QueryParam("b") String b,
@QueryParam("t") String[] t) {
return "Parametr a = " + a
+ "\nParametr b = " + b
+ "\nTablica: " + Arrays.toString(t);
}
// .../api/parametry/query;a=Ala;b=Basia;t=Tomek;t=Tadeusz;t=Tola
@GET
@Path("/matrix")
public String matrix(
@MatrixParam("a") String a,
@MatrixParam("b") String b,
@MatrixParam("t") String[] t) {
return "Parametr a = " + a
+ "\nParametr b = " + b
+ "\nTablica: " + Arrays.toString(t);
}
// /api/parametry/path/Ala/123/98765qwerty
// przykład realnego użycia: /products/{id}
@GET
@Path("/path/{a}/{b}/{cyfry:\\d+}{litery:[A-Za-z]*}")
public String pathParam(
@PathParam("a") String a,
@PathParam("b") String b,
@PathParam("cyfry") String cyfry,
@PathParam("litery") String litery
) {
return "Parametr a = " + a
+ "\nParametr b = " + b
+ "\nCyfry: " + cyfry
+ "\nLitery: " + litery;
}
@GET
@Path("/headers")
public String headers(
@HeaderParam("accept") String accept,
@HeaderParam("user-agent") String agent
) {
return "Accept: " + accept
+ "\nUser-Agent: " + agent;
}
@GET
@Path("/cookies")
public String cookies(
@CookieParam("ciacho") String ciacho,
@CookieParam("JSESSIONID") String sessionId
) {
return "Ciacho: " + ciacho
+ "\nSesja: " + sessionId;
}
@GET
@Path("/ustaw")
// ustawia ciacho
public Response ustawCiacho() {
String ciacho = LocalDateTime.now().toString();
return Response.ok()
.cookie(new NewCookie.Builder("ciacho").value(ciacho).build())
.type("text/plain")
.entity("Ustawiam ciacho na: " + ciacho)
.build();
}
}
package sklep.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import sklep.model.Customer;
public class CustomerDAO {
private final DBConnection db;
CustomerDAO(DBConnection db) {
this.db = db;
}
public Customer findByEmail(String email) throws DBException, RecordNotFound {
final String sql = "SELECT * FROM customers WHERE customer_email = ?";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, email);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return customerFromRS(rs);
} else {
throw new RecordNotFound("Cannot find customer with email " + email);
}
}
} catch (SQLException e) {
throw new DBException("SQL error in CustomerDAO.findById: " + e.getMessage(), e);
}
}
public List<Customer> readAll() throws DBException {
final String sql = "SELECT * FROM customers";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
return customerListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in CustomerDAO.readAll: " + e.getMessage(), e);
}
}
private List<Customer> customerListFromRS(ResultSet rs) throws SQLException {
List<Customer> records = new ArrayList<>();
while (rs.next()) {
Customer product = customerFromRS(rs);
records.add(product);
}
return records;
}
private Customer customerFromRS(ResultSet rs) throws SQLException {
return new Customer(
rs.getString("customer_email"),
rs.getString("customer_name"),
rs.getString("phone_number"),
rs.getString("address"),
rs.getString("postal_code"),
rs.getString("city"));
}
public void insert(Customer customer) throws DBException {
// używać gdy obiekt ma wpisane ID (tu: email)
final String sql = "INSERT INTO customers("
+ "customer_email, customer_name, phone_number, address, postal_code, city)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, customer.getEmail());
stmt.setString(2, customer.getName());
stmt.setString(3, customer.getPhoneNumber());
stmt.setString(4, customer.getAddress());
stmt.setString(5, customer.getPostalCode());
stmt.setString(6, customer.getCity());
stmt.executeUpdate();
} catch (SQLException e) {
throw new DBException("Error during INSERT CUSTOMER", e);
}
}
public boolean update(Customer customer) throws DBException {
final String sql = "UPDATE customers SET "
+ " customer_name=?, phone_number=?, address=?, postal_code=?, city=?"
+ " WHERE customer_email = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, customer.getName());
stmt.setString(2, customer.getPhoneNumber());
stmt.setString(3, customer.getAddress());
stmt.setString(4, customer.getPostalCode());
stmt.setString(5, customer.getCity());
stmt.setString(6, customer.getEmail());
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during UPDATE CUSTOMER", e);
}
}
public void save(Customer customer) throws DBException {
if(customer.getEmail() == null) {
throw new IllegalArgumentException("Customer email cannot be null");
} else {
if(! update(customer)) {
insert(customer);
}
}
}
public boolean delete(String email) throws DBException {
final String sql = "DELETE FROM customers WHERE customer_email = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, email);
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during DELETE CUSTOMER", e);
}
}
public boolean delete(Customer customer) throws DBException {
return delete(customer.getEmail());
}
}
package sklep.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBConnection implements AutoCloseable {
private Connection sqlConnection;
private DBConnection(Connection sqlConnection) {
this.sqlConnection = sqlConnection;
}
public static DBConnection open() throws DBException {
return open(false);
}
public static DBConnection open(boolean autoCommit) throws DBException {
try {
Properties props = DBSettings.load();
if(props.containsKey("driver_class")) {
Class.forName(props.getProperty("driver_class"));
}
Connection c = DriverManager.getConnection(props.getProperty("url") , props);
c.setAutoCommit(autoCommit);
return new DBConnection(c);
} catch (ClassNotFoundException | SQLException e) {
throw new DBException("Cannot connect to postgresql: " + e, e);
}
}
public static DBConnection openLocalhost() throws DBException {
try {
Connection c = DriverManager.getConnection("jdbc:postgresql://localhost/sklep", "alx", "abc123");
c.setAutoCommit(false);
return new DBConnection(c);
} catch (SQLException e) {
throw new DBException("Cannot connect to postgresql: " + e, e);
}
}
@Override
public void close() {
try {
if (sqlConnection != null) {
sqlConnection.close();
sqlConnection = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getSqlConnection() {
return sqlConnection;
}
public void commit() throws DBException {
try {
sqlConnection.commit();
} catch (SQLException e) {
throw new DBException("Error during commit: " + e.getMessage(), e);
}
}
public void rollback() throws DBException {
try {
sqlConnection.rollback();
} catch (SQLException e) {
throw new DBException("Error during rollback: " + e.getMessage(), e);
}
}
public ProductDAO productDAO() {
return new ProductDAO(this);
}
public CustomerDAO customerDAO() {
return new CustomerDAO(this);
}
public OrderDAO orderDAO() {
return new OrderDAO(this);
}
}
package sklep.db;
public class DBException extends SklepException {
public DBException() {
super();
}
public DBException(String message, Throwable cause) {
super(message, cause);
}
public DBException(String message) {
super(message);
}
}
package sklep.db;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class DBSettings {
public static final String DB_SETTINGS_SYSTEM_PROPERTY = "sklep.db_settings_location";
private static final String INTERNAL_DEFAULT_PROPERTIES = "/sklep.properties";
private static DBSettings dbSettings; // singleton
private final Properties props;
private DBSettings() throws DBException {
props = new Properties();
String systemProperty = System.getProperty(DB_SETTINGS_SYSTEM_PROPERTY);
try(InputStream input = systemProperty != null
? new FileInputStream(new File(systemProperty))
: DBSettings.class.getResourceAsStream(INTERNAL_DEFAULT_PROPERTIES) ) {
props.load(input);
} catch (IOException e) {
//e.printStackTrace();
throw new DBException("Cannot read settings. " + e, e);
}
}
public static synchronized DBSettings getInstance() throws DBException {
// Dla klasy typu "singleton" w aplikacji powstaje tylko jedna instancja (obiekt) tej klasy.
// Dostęp do tego obiektu odbywa się poprzez metodę statyczną taką jak ta.
// Tutaj mamy "leniwą inicjalizację", czyli obiekt jest tworzony przy pierwszej próbie dostepu.
if(dbSettings == null) {
dbSettings = new DBSettings();
}
return dbSettings;
}
public Properties getProperties() {
return props;
}
public static Properties load() throws DBException {
return getInstance().getProperties();
}
}
package sklep.db;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import sklep.model.Order;
import sklep.model.OrderProduct;
public class OrderDAO {
private static final String[] ID_COLUMNS = {"order_id", "order_date"};
private final DBConnection db;
OrderDAO(DBConnection db) {
this.db = db;
}
public Order findById(int orderId) throws DBException, RecordNotFound {
final String sql = "SELECT * FROM orders WHERE order_id = ?";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, orderId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return orderFromRS(rs);
} else {
throw new RecordNotFound("Cannot find order with id " + orderId);
}
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.findById: " + e.getMessage(), e);
}
}
public List<Order> readAll() throws DBException {
final String sql = "SELECT * FROM orders ORDER BY order_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
return orderListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.readAll: " + e.getMessage(), e);
}
}
public List<Order> customerOrders(String email) throws DBException {
final String sql = "SELECT * FROM orders WHERE customer_email = ? ORDER BY order_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, email);
try (ResultSet rs = stmt.executeQuery()) {
return orderListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.customerOrders: " + e.getMessage(), e);
}
}
private List<Order> orderListFromRS(ResultSet rs) throws SQLException, DBException {
List<Order> orders = new ArrayList<>();
while (rs.next()) {
Order order = orderFromRS(rs);
orders.add(order);
}
return orders;
}
private Order orderFromRS(ResultSet rs) throws SQLException, DBException {
Order order = Order.ofDbFields(
rs.getInt("order_id"),
rs.getString("customer_email"),
rs.getTimestamp("order_date"),
rs.getString("status"));
order.addProducts(orderProductsForOrder(order.getOrderId()));
return order;
}
List<OrderProduct> orderProductsForOrder(int orderId) throws DBException {
final String sql = "SELECT * FROM order_products WHERE order_id = ? ORDER BY product_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, orderId);
try (ResultSet rs = stmt.executeQuery()) {
List<OrderProduct> ops = new ArrayList<>();
while(rs.next()) {
ops.add(orderProductFromRS(rs));
}
return ops;
}
} catch (SQLException e) {
throw new DBException("SQL error in OrderDAO.customerOrders: " + e.getMessage(), e);
}
}
private OrderProduct orderProductFromRS(ResultSet rs) throws SQLException {
return new OrderProduct(rs.getInt("order_id"), rs.getInt("product_id"), rs.getInt("quantity"), rs.getBigDecimal("actual_price"));
}
}
package sklep.db;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import sklep.model.Product;
/* DAO - Data Access Object(s)
Dostęp do baz danych oparty o takie zasady:
tabelom bazodanowym odpowiadają klasy w naszej aplikacji
np. dla tabeli products mamy klasę Product (w pakiecie model)
dla takie pary tabela products + klasa Product tworzymy klasę narzędziową ProductDAO , której zadaniem jest obsługa tej tabeli: odczyt, zapis, wyszukiwanie, i inne operacje jeśli są potrzebne.
*/
public class ProductDAO {
private static final BigDecimal MAX_PRICE = new BigDecimal(1_000_000_000);
private static final String[] ID_COLUMNS = {"product_id"};
private final DBConnection db;
ProductDAO(DBConnection db) {
this.db = db;
}
public Product findById(int productId) throws DBException, RecordNotFound {
final String sql = "SELECT * FROM products WHERE product_id = ?";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, productId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return productFromRS(rs);
} else {
throw new RecordNotFound("Cannot find product with id " + productId);
}
}
} catch (SQLException e) {
throw new DBException("SQL error in ProductDAO.findById: " + e.getMessage(), e);
}
}
public List<Product> readAll() throws DBException {
final String sql = "SELECT * FROM products ORDER BY product_id";
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
return productListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in ProductDAO.readAll: " + e.getMessage(), e);
}
}
public List<Product> findByPrice(BigDecimal minPrice, BigDecimal maxPrice) throws DBException {
final String sql = "SELECT * FROM products WHERE price BETWEEN ? AND ? ORDER BY product_id";
if(minPrice == null)
minPrice = BigDecimal.ZERO;
if(maxPrice == null)
maxPrice = MAX_PRICE;
try (PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setBigDecimal(1, minPrice);
stmt.setBigDecimal(2, maxPrice);
try (ResultSet rs = stmt.executeQuery()) {
return productListFromRS(rs);
}
} catch (SQLException e) {
throw new DBException("SQL error in ProductDAO.findByPrice: " + e.getMessage(), e);
}
}
private List<Product> productListFromRS(ResultSet rs) throws SQLException {
List<Product> products = new ArrayList<>();
while (rs.next()) {
Product product = productFromRS(rs);
products.add(product);
}
return products;
}
private Product productFromRS(ResultSet rs) throws SQLException {
return new Product(
rs.getInt("product_id"),
rs.getString("product_name"),
rs.getBigDecimal("price"),
rs.getBigDecimal("vat"),
rs.getString("description"));
}
public void insert(Product product) throws DBException {
// używać gdy obiekt ma wpisane ID
final String sql = "INSERT INTO products("
+ " product_id, product_name, price, vat, description)"
+ " VALUES (?, ?, ?, ?, ?)";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, product.getProductId());
stmt.setString(2, product.getProductName());
stmt.setBigDecimal(3, product.getPrice());
stmt.setBigDecimal(4, product.getVat());
stmt.setString(5, product.getDescription());
stmt.executeUpdate();
} catch (SQLException e) {
throw new DBException("Error during INSERT PRODUCT", e);
}
}
public void insertNew(Product product) throws DBException {
// używać gdy obiekt nie ma wpisanego ID (productID == null)
final String sql = "INSERT INTO products("
+ " product_name, price, vat, description)"
+ " VALUES (?, ?, ?, ?)";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql, ID_COLUMNS)) {
stmt.setString(1, product.getProductName());
stmt.setBigDecimal(2, product.getPrice());
stmt.setBigDecimal(3, product.getVat());
stmt.setString(4, product.getDescription());
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if(rs.next()) {
// w obiekcie, który mamy w pamięci, uzupełniamy brakujące ID na podstawie tego, co wygenerowała baza
product.setProductId(rs.getInt(1));
}
};
} catch (SQLException e) {
throw new DBException("Error during INSERT PRODUCT", e);
}
}
public boolean update(Product product) throws DBException {
final String sql = "UPDATE products SET "
+ " product_name = ?, price = ?, vat = ?, description = ?"
+ " WHERE product_id = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setString(1, product.getProductName());
stmt.setBigDecimal(2, product.getPrice());
stmt.setBigDecimal(3, product.getVat());
stmt.setString(4, product.getDescription());
stmt.setInt(5, product.getProductId());
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during UPDATE PRODUCT", e);
}
}
public void save(Product product) throws DBException {
if(product.getProductId() == null) {
// wstawiamy nowy rekord korzystajac z sekwecji
insertNew(product);
} else {
if(! update(product)) {
insert(product);
}
}
}
public boolean delete(int productId) throws DBException {
final String sql = "DELETE FROM products WHERE product_id = ?";
try(PreparedStatement stmt = db.getSqlConnection().prepareStatement(sql)) {
stmt.setInt(1, productId);
int count = stmt.executeUpdate();
return count > 0;
} catch (SQLException e) {
throw new DBException("Error during DELETE PRODUCT", e);
}
}
public boolean delete(Product product) throws DBException {
return delete(product.getProductId());
}
}
package sklep.db;
public class RecordNotFound extends SklepException {
public RecordNotFound() {
super();
}
public RecordNotFound(String message) {
super(message);
}
}
package sklep.db;
public class SklepException extends Exception {
public SklepException() {
super();
}
public SklepException(String message, Throwable cause) {
super(message, cause);
}
public SklepException(String message) {
super(message);
}
public SklepException(Throwable cause) {
super(cause);
}
}
package sklep.model;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDateTime;
public class AdapterDaty extends XmlAdapter<String, LocalDateTime> {
@Override
public LocalDateTime unmarshal(String s) {
return LocalDateTime.parse(s);
}
@Override
public String marshal(LocalDateTime dt) {
return dt.toString();
}
}
package sklep.model;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.Objects;
@XmlRootElement
public class Customer {
private String email;
private String name;
private String phoneNumber;
private String address;
private String postalCode;
private String city;
public Customer() {
}
public Customer(String email, String name, String phone, String address, String postalCode, String city) {
this.email = email;
this.name = name;
this.phoneNumber = phone;
this.address = address;
this.postalCode = postalCode;
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phone) {
this.phoneNumber = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Customer [email=" + email + ", name=" + name + ", phone=" + phoneNumber + ", address=" + address
+ ", postalCode=" + postalCode + ", city=" + city + "]";
}
@Override
public int hashCode() {
return Objects.hash(email, name, address, city, phoneNumber, postalCode);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
return Objects.equals(email, other.email) && Objects.equals(name, other.name)
&& Objects.equals(address, other.address) && Objects.equals(city, other.city)
&& Objects.equals(phoneNumber, other.phoneNumber) && Objects.equals(postalCode, other.postalCode);
}
}
package sklep.model;
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;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@XmlRootElement
public class Order {
@XmlAttribute(name="id")
private Integer orderId;
@XmlElement(name="customer-email")
private String customerEmail;
@XmlElement(name="order-date")
@XmlJavaTypeAdapter(AdapterDaty.class)
private LocalDateTime orderDate;
@XmlAttribute(name="status")
private Status orderStatus;
@XmlElementWrapper(name="products")
@XmlElement(name="product")
public final List<OrderProduct> products = new ArrayList<>();
public Order() {
}
public Order(Integer orderId, String customerEmail, LocalDateTime orderDate, Status orderStatus) {
this.orderId = orderId;
this.customerEmail = customerEmail;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
}
public static Order ofDbFields(int orderId, String customerEmail, java.sql.Timestamp orderDate, String orderStatus) {
return new Order(orderId, customerEmail,
orderDate.toLocalDateTime(),
Status.valueOf(orderStatus.toUpperCase()));
}
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public String getCustomerEmail() {
return customerEmail;
}
// public void setCustomerEmail(String customerEmail) {
// this.customerEmail = customerEmail;
// }
public LocalDateTime getOrderDate() {
return orderDate;
}
public void setOrderDate(LocalDateTime orderDate) {
this.orderDate = orderDate;
}
public Status getOrderStatus() {
return orderStatus;
}
public void setOrderStatus(Status orderStatus) {
this.orderStatus = orderStatus;
}
public List<OrderProduct> getProducts() {
return Collections.unmodifiableList(products);
}
public void addProduct(OrderProduct product) {
this.products.add(product);
}
public void addProducts(Collection<OrderProduct> products) {
this.products.addAll(products);
}
public void setProducts(Collection<OrderProduct> products) {
this.products.clear();
this.products.addAll(products);
}
@Override
public String toString() {
return "Order [orderId=" + orderId + ", customerEmail=" + customerEmail + ", orderDate=" + orderDate
+ ", orderStatus=" + orderStatus + "]";
}
@Override
public int hashCode() {
return Objects.hash(customerEmail, orderDate, orderId, orderStatus);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Order other = (Order) obj;
return Objects.equals(customerEmail, other.customerEmail) && Objects.equals(orderDate, other.orderDate)
&& Objects.equals(orderId, other.orderId) && orderStatus == other.orderStatus;
}
public enum Status {
NEW,
CONFIRMED,
PAID,
SHIPPED,
CLOSED,
RETURNED,
;
}
}
package sklep.model;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import java.math.BigDecimal;
import java.util.Objects;
public class OrderProduct {
@XmlAttribute(name="order-id")
private Integer orderId;
@XmlAttribute(name="product-id")
private Integer productId;
private int quantity;
@XmlElement(name="price")
private BigDecimal actualPrice;
public OrderProduct() {
}
public OrderProduct(Integer orderId, Integer productId, int quantity, BigDecimal actualPrice) {
this.orderId = orderId;
this.productId = productId;
this.quantity = quantity;
this.actualPrice = actualPrice;
}
public static OrderProduct of(Integer orderId, Product product, int quantity) {
return new OrderProduct(orderId, product.getProductId(), quantity, product.getPrice());
}
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public BigDecimal getActualPrice() {
return actualPrice;
}
public void setActualPrice(BigDecimal actualPrice) {
this.actualPrice = actualPrice;
}
@Override
public String toString() {
return "OrderProduct [orderId=" + orderId + ", productId=" + productId + ", quantity=" + quantity
+ ", actualPrice=" + actualPrice+ "]";
}
@Override
public int hashCode() {
return Objects.hash(orderId, productId, quantity, actualPrice);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrderProduct other = (OrderProduct) obj;
return Objects.equals(orderId, other.orderId) && Objects.equals(productId, other.productId)
&& quantity == other.quantity
&& Objects.equals(actualPrice, other.actualPrice);
}
}
package sklep.model;
import java.math.BigDecimal;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlValue;
@XmlRootElement
public class Price {
@XmlValue
private BigDecimal value;
public Price() {
this.value = BigDecimal.ZERO;
}
public Price(BigDecimal value) {
this.value = value;
}
public BigDecimal getValue() {
return value;
}
public void setValue(BigDecimal value) {
this.value = value;
}
@Override
public String toString() {
return value.toString();
}
}
package sklep.model;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.math.BigDecimal;
import java.util.Objects;
@XmlRootElement
public class Product {
@XmlAttribute(name="id")
private Integer productId;
@XmlElement(name="product-name")
private String productName;
private BigDecimal price;
private BigDecimal vat;
private String description;
public Product() {
}
public Product(Integer productId, String productName, BigDecimal price, BigDecimal vat, String description) {
this.productId = productId;
this.productName = productName;
this.price = price;
this.vat = vat;
this.description = description;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getVat() {
return vat;
}
public void setVat(BigDecimal vat) {
this.vat = vat;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int hashCode() {
return Objects.hash(description, price, vat, productId, productName);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Product other = (Product) obj;
return Objects.equals(productId, other.productId) && Objects.equals(productName, other.productName)
&& Objects.equals(price, other.price)
&& Objects.equals(vat, other.vat)
&& Objects.equals(description, other.description);
}
@Override
public String toString() {
return "Product [productId=" + productId + ", productName=" + productName + ", price=" + price + ", vat=" + vat
+ ", description=" + description + "]";
}
public String toHtml() {
return String.format("<div class='product'>"
+ "<h2>%s</h2>"
+ "<p>(nr %d)</p>"
+ "<p>Cena: <strong>%,.2f PLN</strong></p>"
+ "<p>%s</p>"
+ "</div>",
getProductName(),
getProductId(),
getPrice(),
getDescription());
}
}
package sklep.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="products")
public class ProductList {
@XmlElement(name="product")
private final List<Product> products = new ArrayList<>();
public ProductList() {
// zostawia pustą listę
}
public ProductList(Collection<Product> products) {
this.products.addAll(products);
}
public List<Product> getProducts() {
return Collections.unmodifiableList(this.products);
}
public void setProducts(Collection<Product> products) {
this.products.clear();
this.products.addAll(products);
}
@Override
public String toString() {
return this.products.toString();
}
}
@XmlAccessorType(XmlAccessType.FIELD)
package sklep.model;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
\ No newline at end of file
package sklep.rest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import sklep.db.DBException;
import sklep.db.DBSettings;
import sklep.db.RecordNotFound;
public class PhotoUtil {
private static final String EXT = ".jpg";
public static File getFile(int productId) throws DBException, RecordNotFound {
Path path = getPath(productId);
File file = path.toFile();
if(file.exists()) {
return file;
} else {
throw new RecordNotFound("Cannot read photo for product id = " + productId);
}
}
public static byte[] readBytes(int productId) throws DBException, RecordNotFound {
Path path = getPath(productId);
try {
return Files.readAllBytes(path);
} catch (IOException e) {
// System.err.println(e);
throw new RecordNotFound("Cannot read photo for product id = " + productId);
}
}
public static void writeStream(int productId, InputStream inputStream) {
try {
Path path = getPath(productId);
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
// wypisujemy błąd, ale metoda kończy się normalnie
e.printStackTrace();
}
}
public static void writeBytes(int productId, byte[] bytes) {
try {
Path path = getPath(productId);
Files.write(path, bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
private static Path getPath(int productId) throws DBException {
String dir = DBSettings.load().getProperty("photo_dir");
String fileName = productId + EXT;
return Paths.get(dir, fileName);
}
}
package sklep.rest;
import java.util.List;
import jakarta.enterprise.context.RequestScoped;
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.Response;
import jakarta.ws.rs.core.UriBuilder;
import sklep.db.CustomerDAO;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.RecordNotFound;
import sklep.model.Customer;
@RequestScoped
@Path("/customers")
@Produces({ "application/xml", "application/json" })
@Consumes({ "application/xml", "application/json" })
public class RCustomer {
@POST
public Response save(final Customer customer) throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
customerDAO.save(customer);
db.commit();
return Response.created(UriBuilder.fromResource(RCustomer.class).path(String.valueOf(customer.getEmail())).build()).build();
}
}
@GET
@Path("/{id}")
public Customer findById(@PathParam("id") final String email) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
return customerDAO.findByEmail(email);
}
}
@GET
public List<Customer> listAll() throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
return customerDAO.readAll();
}
}
@PUT
@Path("/{id}")
public Response update(@PathParam("id") String email, final Customer customer) throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
customer.setEmail(email);
customerDAO.save(customer);
db.commit();
}
return Response.noContent().build();
}
@DELETE
@Path("/{id}")
public Response deleteById(@PathParam("id") String email) throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
customerDAO.delete(email);
db.commit();
}
return Response.noContent().build();
}
}
package sklep.rest;
import java.net.URI;
import java.util.List;
import jakarta.enterprise.context.RequestScoped;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.UriBuilder;
import sklep.db.CustomerDAO;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.OrderDAO;
import sklep.db.RecordNotFound;
import sklep.model.Customer;
import sklep.model.Order;
@RequestScoped
@Path("/orders")
@Produces({ "application/xml", "application/json" })
@Consumes({ "application/xml", "application/json" })
public class ROrder {
@GET
public List<Order> listAll() throws DBException {
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
return orderDAO.readAll();
}
}
@GET
@Path("/{id:[0-9][0-9]*}")
public Response findById(@PathParam("id") final Integer id) {
// Klasa Response pozwala nam z pełną precyzją przygotować odpowiedź, która ma zostać odesłana klientowi.
// W przypadku pozytywnym (ok) zostanie odesłany obiekt przetłumaczony na XML lub JSON, a kod wynikowy to będzie 200.
// Ale w przypadku błędów możemy sami zdecydować co odsyłami (tutaj odpowiednie kody HTTP).
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
Order order = orderDAO.findById(id);
return Response.ok(order).build();
} catch (DBException e) {
e.printStackTrace();
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
} catch (RecordNotFound e) {
return Response.status(Status.NOT_FOUND).build();
}
}
/*
// Metoda, która ma obsłużyć pobranie info o właścicielu zamówienia:
// /orders/1/customer
// W tej wersji metoda zwraca bezpośrednio dane klienta.
// Wada tego podejścia: ten sam rekord (konkretny klient) jest widoczny pod różnymi adresami URL.
@GET
@Path("/{id:[0-9][0-9]*}/customer")
public Customer getCustomer(@PathParam("id") Integer orderId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
CustomerDAO customerDAO = db.customerDAO();
Order order = orderDAO.findById(orderId);
Customer customer = customerDAO.findByEmail(order.getCustomerEmail());
return customer;
}
}
*/
// W tej wersji w odpowiedzi na zapytanie o dane klienta, który złożył zamówienie,
// wyślemy przekierowanie pod adres tego klienta.
// To jest lepsze z punktu widzenia "dobrych praktyk REST".
@GET
@Path("/{id:[0-9][0-9]*}/customer")
public Response getCustomer(@PathParam("id") Integer orderId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
Order order = orderDAO.findById(orderId);
URI customerURI = UriBuilder.fromResource(RCustomer.class)
.path("/{email}")
.build(order.getCustomerEmail());
return Response.seeOther(customerURI).build();
}
}
}
package sklep.rest;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.model.ProductList;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
/* W tej klasie wejdziemy nawet do wnętrza produktów i udostępnimy osobno cenę
* /products/2/price
* W praktyce nie robi się tego zbyt często, ale tu zobaczymy jako przykład możliwości.
*
* To od programisty (twórcy usługi) zależy jakie adresy i jakie operacje pod tymi adresami udostępnia.
*
* Ta klasa udostępnia (i przyjmuje) dane produktów w różnych formatach.
* Gdy w Produces jest wiele formatów, to klient może wybrać za pomocą nagłówka Accept
* Gdy w Consumes jest wiele formatów, to klient może przysłać dane w dowolnym z nich (nagłówek Content-Type)
*/
@Path("/products")
public class RProducts {
@GET
@Produces({"application/json", "text/plain;charset=UTF-8"})
// @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
public List<Product> allProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.readAll();
}
}
@GET
@Produces({"application/xml", "application/pdf"})
public ProductList allProductsXml() throws DBException {
return new ProductList(allProducts());
}
// Może też być tak, że kilka metod działa pod tym samym adresem, ale służą one do tworzenia odpowiedzi w różnych formatach.
// Przykład: tworzenie HTML w oddzielnej metodzie
@GET
@Produces("text/html;charset=UTF-8")
public String readAllHTML() throws DBException {
List<Product> products = allProducts();
StringBuilder txt = new StringBuilder("<!DOCTYPE html>\n<html><body>\n");
txt.append("<h1>Lista produktów</h1>\n");
for(Product product : products) {
txt.append(product.toHtml()).append('\n');
}
txt.append("</body></html>");
return txt.toString();
}
@GET
@Path("/{id}")
@Produces({"application/json", "application/xml", "text/plain;charset=UTF-8", "application/pdf"})
// przykładowo /api/products/3
public Product oneProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
@GET
@Produces("text/html;charset=UTF-8")
@Path("/{id}")
public String readOneHTML(@PathParam("id") int productId) throws DBException, RecordNotFound {
Product product = oneProduct(productId);
return "<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>";
}
// W praktyce REST metoda POST jest używana do dodawania nowych rekordów do katalogu.
// Może być także używana w innych celach - gdy klient ma "przysłać dane na serwer", a serwer coś z tym zrobi (podobnie jak to było w SOAP). @POST
// POST w tym miejscu jest lepszy niż PUT, bo zapisując nowy rekord, nie wiemy z góry jakie będzie będzie jego ID,
// czyli nie wiemy, pod adresem zapisze się nowy produkt.
// POST potrafi "dodać rekord do katalogu".
@POST
@Consumes({"application/json", "application/xml"})
public Response saveProduct(Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
productDAO.save(product);
db.commit();
}
URI uri = UriBuilder.fromResource(RProducts.class).path("/{id}").build(product.getProductId());
return Response.created(uri).build();
}
// Nie praktykuje się tego zbyt często, ale można zdefiniować dedykowane metody dające dostęp do poszczególnych pól obiektu (aby nie transferować całego rekordu, gdy potrzebna tylko jedna informacja)
@GET
@Path("/{id}/price")
// przykładowo /api/products/3/price
@Produces({"application/json", "text/plain"})
public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
return oneProduct(productId).getPrice();
}
// Operacja HTTP PUT zapisuje zasób pod podanym adresem.
// W zapytaniach typu PUT i POST klient wysyła dane na serwer (treść zapytania: "body" / "entity" / "content" ...)
// W JAX-RS metoda może posiadać tylko jeden parametr bez adnotacji i to właśnie przez ten parametr przekazywana jest treść zapytania
// (te dane, które przysłał klient). Format tych danych opisuje adnotacja @Consumes
@PUT
@Path("/{id}/price")
@Consumes({"application/json", "text/plain"})
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.save(product);
db.commit();
}
}
// PUT vs POST
// PUT powinniśmy używać wtedy, gdy z góry wiadomo, pod jakim adresem zostaną zapisane dane.
// W praktyce PUT używa się najczęściej do aktualizacji istniejących danych, ale teoretycznie PUT
// można też użyć do zapisania nowych danych pod konkretnym adresem.
// Gdy wyślemy dane za pomocą PUT pod adres, to następnie GET z tego samego adresu powinien odczytać dane, które wysłał PUT (być może w innym formacie).
// POST używajmy wtedy, gdy nie wiemy z góry pod jakim adresem dane zostaną zapisane, np. gdy id rekordu jest generowane z sekwencji.
// Taka metoda powinna dać w odpowiedzi informację o ID utworzonego rekordu.
// Kilka możliwości:
// 1) odesłanie uzupełnionego rekordu - trochę niewydajne, ale wygodne
// 2) minimalny dokumencik JSON, w którego wnętrzu jest zawarta ta informacja (tak robi wiele usług w praktyce, np. PayU)
// 3) (teraz w tej wersji) - odesłać odpowiedź typu Created z nagłówkiem Location - najlepsze z punktu widzenia standardów/dobrych praktyk
// Aby zwracać taie "techniczne" odpowiedzi, używa się klasy Response.
@DELETE
@Path("/{id}")
public void usun(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
productDAO.delete(productId);
db.commit();
}
}
@GET
@Path("/{id}/photo")
// przykładowo /api/products/3/photo
@Produces("image/jpeg")
public byte[] getPhoto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
@PUT
@Path("/{id}/photo")
@Consumes("image/jpeg")
public void getPhoto(@PathParam("id") int productId, byte[] bajty) {
PhotoUtil.writeBytes(productId, bajty);
}
}
package sklep.rest;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import java.util.List;
@Path("/products.html")
@Produces("text/html;charset=UTF-8")
public class RProductsHtml {
@GET
public String allProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
List<Product> products = productDAO.readAll();
StringBuilder txt = new StringBuilder("<!DOCTYPE html>\n<html><body>\n");
txt.append("<h1>Lista produktów</h1>\n");
for(Product product : products) {
txt.append(product.toHtml()).append('\n');
}
txt.append("</body></html>");
return txt.toString();
}
}
// W tej metodzie pokazuję, jak wyglądaloby zwracanie różnych kodów odpowiedzi w zależności od sytuacji.
// Używamy klasy Response
@GET
@Path("/{id}")
public Response oneProduct(@PathParam("id") int productId) {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
return Response.ok()
.entity("<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>")
.build();
} catch (DBException e) {
return Response.serverError()
.entity("<!DOCTYPE html>\n<html><body><p>WIELKA BIEDA</p></body></html>")
.build();
} catch (RecordNotFound e) {
return Response.status(404)
.entity("<!DOCTYPE html>\n<html><body><p>BRAK TAKIEGO PRODUKTU</p></body></html>")
.build();
}
}
}
package sklep.rest;
import jakarta.ws.rs.*;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import java.math.BigDecimal;
import java.util.List;
@Path("/products.json")
@Produces("application/json")
@Consumes("application/json")
public class RProductsJson {
@GET
public List<Product> allProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.readAll();
}
}
@GET
@Path("/{id}")
// przykładowo /api/products.json/3
public Product oneProduct(@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();
}
}
// Nie praktykuje się tego zbyt często, ale można zdefiniować dedykowane metody dające dostęp do poszczególnych pól obiektu (aby nie transferować całego rekordu, gdy potrzebna tylko jedna informacja)
@GET
@Path("/{id}/price")
public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
return oneProduct(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.save(product);
db.commit();
}
}
@GET
@Path("/{id}/photo")
@Produces("image/jpeg")
public byte[] getPhoto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
@PUT
@Path("/{id}/photo")
@Consumes("image/jpeg")
public void getPhoto(@PathParam("id") int productId, byte[] bajty) {
PhotoUtil.writeBytes(productId, bajty);
}
}
package sklep.rest;
import jakarta.ws.rs.*;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.model.ProductList;
@Path("/products.pdf")
@Produces("application/pdf")
public class RProductsPdf {
@GET
public ProductList allProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return new ProductList(productDAO.readAll());
}
}
@GET
@Path("/{id}")
// przykładowo /api/products.pdf/3
public Product oneProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
}
package sklep.rest;
import jakarta.ws.rs.*;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Price;
import sklep.model.Product;
import sklep.model.ProductList;
import java.math.BigDecimal;
import java.util.List;
@Path("/products.xml")
@Produces("application/xml")
@Consumes("application/xml")
public class RProductsXml {
@GET
public ProductList allProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return new ProductList(productDAO.readAll());
}
}
@GET
@Path("/{id}")
// przykładowo /api/products.xml/3
public Product oneProduct(@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 Price getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
return new Price(oneProduct(productId).getPrice());
}
@PUT
@Path("/{id}/price")
public void setPrice(@PathParam("id") int productId, Price newPrice) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
product.setPrice(newPrice.getValue());
productDAO.save(product);
db.commit();
}
}
@GET
@Path("/{id}/photo")
@Produces("image/jpeg")
public byte[] getPhoto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
@PUT
@Path("/{id}/photo")
@Consumes("image/jpeg")
public void getPhoto(@PathParam("id") int productId, byte[] bajty) {
PhotoUtil.writeBytes(productId, bajty);
}
}
package sklep.rest.ext;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.WebApplicationException;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.util.JAXBSource;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.xmlgraphics.util.MimeConstants;
public class ObslugaXSL {
private ServletContext servletContext;
public ObslugaXSL(ServletContext servletContext) {
this.servletContext = servletContext;
}
// obiekt -(za pomocą JAXB)-> XML -(za pomocą transformera)-> XML(XSL-FO)
// --(za pomocą Apache FOP)-> PDF -> output
public void wypiszPDF(Object obj, OutputStream output) {
try(InputStream configStream = servletContext.getResourceAsStream("WEB-INF/fop-conf.xml")) {
JAXBContext ctx = JAXBContext.newInstance(obj.getClass());
JAXBSource src = new JAXBSource(ctx, obj);
FopFactory fopFactory = FopFactory.newInstance(new URI(""), configStream);
try(BufferedOutputStream pdfOut = new BufferedOutputStream(output)) {
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, pdfOut);
TransformerFactory tf = TransformerFactory.newInstance();
StreamSource xsl = new StreamSource(servletContext.getResourceAsStream("WEB-INF/sklep-fo.xsl"));
Transformer tr = tf.newTransformer(xsl);
Result res = new SAXResult(fop.getDefaultHandler());
tr.transform(src, res);
}
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException("Problem FOP " + e.getMessage(), e, 500);
}
}
}
package sklep.rest.ext;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Provider;
import sklep.model.Product;
import sklep.model.ProductList;
@Provider
public class PdfWriter implements MessageBodyWriter<Object> {
private static final MediaType PDF_TYPE = new MediaType("application", "pdf");
@Context
private ServletContext servletContext;
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return (type == ProductList.class || type == Product.class) && PDF_TYPE.isCompatible(mediaType);
}
@Override
public void writeTo(Object obj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream output)
throws IOException, WebApplicationException {
String fileName = "products.pdf";
if (obj instanceof Product) {
Product product = (Product) obj;
// fileName = product.getProductName().replace(' ', '_') + ".pdf";
fileName = String.format("product%04d.pdf", product.getProductId());
}
// httpHeaders.add("Content-Disposition", "attachment;filename=" + fileName);
httpHeaders.add("Content-Disposition", "inline;filename=" + fileName);
ObslugaXSL obslugaXSL = new ObslugaXSL(servletContext);
obslugaXSL.wypiszPDF(obj, output);
}
}
package sklep.rest.ext;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Provider;
import sklep.model.Product;
@Provider
public class Product2HtmlWriter implements MessageBodyWriter<Product> {
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
System.out.println("Sprawdzam isWriteable " + type + " " + mediaType);
// Na podstawie informacji odczytanych z kodu zw. z metodą zwracającą wynik
// mamy odpowiedzieć na pytanie "czy ten writer sobie z tym poradzi".
return type == Product.class && mediaType.isCompatible(MediaType.TEXT_HTML_TYPE);
}
@Override
public void writeTo(Product product, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream out)
throws IOException, WebApplicationException {
System.out.println("Wykonuję writeTo " + type + " " + mediaType + " dla obiektu " + product);
// Dla konkretnego obiektu mamy go wypisać w podanym formacie przez przekazany nam OutputStream.
String html = "<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>";
httpHeaders.add("Content-Type", "text/html;charset=utf-8");
out.write(html.getBytes("utf-8"));
}
}
package sklep.rest.ext;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import sklep.db.RecordNotFound;
// Gdy taka klasa jest obecna w projekcie, to w każdej sytuacji, gdy metoda "restowa" kończy się wyjątkiem
// RecordNotFound, serwer odeśle odpowiedź przygotowaną przez tego mappera.
@Provider
public class RecordNotFoundMapper implements ExceptionMapper<RecordNotFound> {
@Override
public Response toResponse(RecordNotFound exception) {
String html = """
<html><body>
<h1>Nie znaleziono</h1>
<p style='color:red'>
""" + exception.getMessage()
+ "</p></body></html>";
return Response.status(404)
.type("text/html")
.entity(html)
.build();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_4_0.xsd">
</beans>
\ No newline at end of file
url=jdbc:postgresql://localhost:5432/sklep
driver_class=org.postgresql.Driver
user=alx
password=abc123
photo_dir=/home/patryk/sklep/foto
# Sciezki na Windows: albo piszemy slashe / , albo podwojne backslashe \\
# photo_dir=C:/Users/Patryk/Desktop/sklep/foto
# photo_dir=C:\\Users\\Patryk\\Desktop\\sklep\\foto
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- $Id: fop.xconf 447325 2006-09-18 08:55:33Z jeremias $ -->
<!--
This is an example configuration file for FOP.
This file contains the same settings as the default values
and will have no effect if used unchanged.
Relative config url's will be resolved relative to
the location of this file.
-->
<!-- NOTE: This is the version of the configuration -->
<fop version="1.0">
<!-- Base URL for resolving relative URLs -->
<base>.</base>
<!-- Source resolution in dpi (dots/pixels per inch) for determining the size of pixels in SVG and bitmap images, default: 72dpi -->
<source-resolution>72</source-resolution>
<!-- Target resolution in dpi (dots/pixels per inch) for specifying the target resolution for generated bitmaps, default: 72dpi -->
<target-resolution>72</target-resolution>
<!-- Default page-height and page-width, in case
value is specified as auto -->
<default-page-settings height="11in" width="8.26in"/>
<!-- Information for specific renderers -->
<!-- Uses renderer mime type for renderers -->
<renderers>
<renderer mime="application/pdf">
<filterList>
<!-- provides compression using zlib flate (default is on) -->
<value>flate</value>
<!-- encodes binary data into printable ascii characters (default off)
This provides about a 4:5 expansion of data size -->
<!-- <value>ascii-85</value> -->
<!-- encodes binary data with hex representation (default off)
This filter is not recommended as it doubles the data size -->
<!-- <value>ascii-hex</value> -->
</filterList>
<fonts>
<!-- embedded fonts -->
<!--
This information must exactly match the font specified
in the fo file. Otherwise it will use a default font.
For example,
<fo:inline font-family="Arial" font-weight="bold" font-style="normal">
Arial-normal-normal font
</fo:inline>
for the font triplet specified by:
<font-triplet name="Arial" style="normal" weight="bold"/>
If you do not want to embed the font in the pdf document
then do not include the "embed-url" attribute.
The font will be needed where the document is viewed
for it to be displayed properly.
possible styles: normal | italic | oblique | backslant
possible weights: normal | bold | 100 | 200 | 300 | 400
| 500 | 600 | 700 | 800 | 900
(normal = 400, bold = 700)
-->
<!--
<font kerning="yes" embed-url="/usr/share/fonts/truetype/msttcorefonts/arial.ttf">
<font-triplet name="Arial" style="normal" weight="normal"/>
<font-triplet name="ArialMT" style="normal" weight="normal"/>
</font>
<font kerning="yes" embed-url="/usr/share/fonts/truetype/msttcorefonts/arial.ttf">
<font-triplet name="Arial" style="normal" weight="normal"/>
<font-triplet name="ArialMT" style="normal" weight="normal"/>
</font>
-->
<!-- PC -->
<directory recursive="true">/usr/share/fonts/truetype/</directory>
<directory recursive="true">C:\Windows\Fonts</directory>
</fonts>
<!-- This option lets you specify additional options on an XML handler -->
<!--xml-handler namespace="http://www.w3.org/2000/svg">
<stroke-text>false</stroke-text>
</xml-handler-->
</renderer>
<renderer mime="application/postscript">
<!-- This option forces the PS renderer to rotate landscape pages -->
<!--auto-rotate-landscape>true</auto-rotate-landscape-->
<!-- This option lets you specify additional options on an XML handler -->
<!--xml-handler namespace="http://www.w3.org/2000/svg">
<stroke-text>false</stroke-text>
</xml-handler-->
</renderer>
<renderer mime="application/vnd.hp-PCL">
</renderer>
<!-- MIF does not have a renderer
<renderer mime="application/vnd.mif">
</renderer>
-->
<renderer mime="image/svg+xml">
<format type="paginated"/>
<link value="true"/>
<strokeText value="false"/>
</renderer>
<renderer mime="application/awt">
</renderer>
<renderer mime="image/png">
<!--transparent-page-background>true</transparent-page-background-->
</renderer>
<renderer mime="image/tiff">
<!--transparent-page-background>true</transparent-page-background-->
<!--compression>CCITT T.6</compression-->
</renderer>
<renderer mime="text/xml">
</renderer>
<!-- RTF does not have a renderer
<renderer mime="text/rtf">
</renderer>
-->
<renderer mime="text/plain">
<pageSize columns="80"/>
</renderer>
</renderers>
</fop>
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