Commit dca054b2 by Patryk Czarnik

ProductController - pełna wersja

parent 9f972f10
package sklep.photo;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
@Component
public class PhotoUtil {
private static final String EXT = ".jpg";
@Value("${alx.photo_dir}")
private String dir;
private Path getPath(int productId) {
String fileName = productId + EXT;
return Paths.get(dir, fileName);
}
public File getFile(int productId) {
Path path = getPath(productId);
File file = path.toFile();
if(file.exists()) {
return file;
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cannot read photo for product id = " + productId);
}
}
public byte[] readBytes(int productId) {
Path path = getPath(productId);
try {
return Files.readAllBytes(path);
} catch(IOException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cannot read photo for product id = " + productId);
}
}
}
package sklep.rest;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import sklep.model.Product;
import sklep.photo.PhotoUtil;
import sklep.repository.ProductRepository;
/* RestController to jest taki Controller, który nie uzywa szablonów, tylko wyniki metod odsyła w treści odpowiedzi do klienta.
* Inaczej mówiąc działa tak, jakby każda metoda miała @ResponseBody.
*
* Najczęściej na poziomie klasy umieszcza się też @RequestMapping z ogólnym adresem, pod którym działa cała ta klasa.
* Metody wewnątrz mogą mieć podany "dalszy ciąg adresu".
*/
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductRepository productRepository;
private PhotoUtil photoUtil;
// W tej klasie stosujemy wstrzykiwanie poprzez konstruktor,
// czyli nie piszemy @Autowired przed polem productRepository,
// tylko tworzymy konstruktor, który wymaga podania tego obiektu.
public ProductController(ProductRepository productRepository, PhotoUtil photoUtil) {
this.productRepository = productRepository;
this.photoUtil = photoUtil;
}
@GetMapping
public List<Product> readAll() {
public List<Product> getAllProducts() {
return productRepository.findAll();
}
@GetMapping("/{id}")
public Product readOne(@PathVariable("id") int productId) {
Optional<Product> product = productRepository.findById(productId);
if(product.isPresent()) {
return product.get();
public Product getOneProduct(@PathVariable Integer id) {
Optional<Product> product = productRepository.findById(id);
return product.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Brak produktu nr " + id));
}
/*
* Tylko jako przykład możliwości: odczyt wybranego pola z rekordu. To twórca
* usługi decyduje o tym, jakie adresy będą dostępne i co pod nimi będzie. Jeśli
* uważamy, że klientom "przyda się" bezpośredni dostęp do wybranego pola, to
* możemy stworzyć taką metodę, ale nie ma żadnego obowiązku, aby robić to dla
* wszystkich pól.
*/
@GetMapping("/{id}/price")
public BigDecimal getPrice(@PathVariable("id") Integer productId) {
return getOneProduct(productId).getPrice();
}
/*
* Operacja PUT służy do zapisania danych POD PODANYM ADRESEM. Na przykład PUT
* products/2/price z wartością 100 powinno ustawić w produkcie nr 2 cenę 100.
* Jeśli PUT zadziała, to następnie GET wysłany pod ten sam adres powinien
* odczytać te same dane, które PUT zapisał. (być może w innym formacie - to
* inny temat)
*
* PUT najczęściej jest używany do aktualizacji istniejących danych
* (pojedynczych wartości albo całych rekordów), ale może być też użyty do
* zapisania nowych danych. To, co najważniejsze, to fakt, że PUT zapisuje dane
* pod konkretnym adresem, do którego jest wysyłany.
*/
@PutMapping("/{id}/price")
public void setPrice(@PathVariable("id") Integer productId, @RequestBody BigDecimal newPrice) {
Optional<Product> optionalProduct = productRepository.findById(productId);
if (optionalProduct.isPresent()) {
Product product = optionalProduct.get();
product.setPrice(newPrice);
productRepository.save(product);
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND); // 404
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Brak produktu nr " + productId);
}
}
/*
* PUT może również służyć do zapisania całego rekordu, ale zwn, że musi być
* skierowany pod ten adres, pod którym rekord zostanie faktycznie zapisany, w
* praktyce PUT jest uzywany do aktualizacji rekordów (UPDATE).
*
* Aby w aplikacji Springowej, w której jest właczone security, działały zapytania POST i PUT,
* trzeba wyłączyć zabezpieczenie "CSRF":
* .and().csrf().disable()
*/
@PutMapping("/{id}")
public void update(@PathVariable("id") Integer productId, @RequestBody Product product) {
// Aby mieć pewność, że zapisujemu produkt o typ ID, wpisuję productId z URL-a.
// Ewentualnie możnaby jeszcze sprawdzić czy rekord istnieje, czy ID się zgadza
// i jeśli coś jest nie tak, to wyrzucić wyjątek.
product.setProductId(productId);
productRepository.save(product);
}
/*
* POST jest najbardziej ogólną metodą HTTP; oznacza, że klient
* "wysyła jakieś dane na serwer", a serwer odsyła jakąś odpowiedź. W zasadzie
* POST może słuzyć do wszystkiego.
*
* W praktyce POST bardzo często służy do dodawania nowych rekordów, ponieważ
* gdy tworzymy nowy rekord, to nie znamy z góry jego ID i nie wiemy pod jakim
* URL-em zostanie zapisany (nie da się więc użyć PUT). Stąd wzięła REST-owa
* konwencja, że aby dodać nowy rekord do katalogu, wysyła się POST z danymi
* tego rekordu pod ogólny adres całego katalogu.
*/
@PostMapping
public Product insert(@RequestBody Product product) {
// Aby mieć pewność, że zapytanie tworzy nowy rekord, ustawwiam productId na
// null.
product.setProductId(null);
productRepository.save(product);
/*
* Operacja save (a wewnętrznie persist z Hibernate) spowoduje ustawienie nowego
* ID w obiekcie. Warto taką informację przekazać klientowi. Można: 1) odesłać
* uzupełniony rekord (i tak zrobimy tutaj), 2) odesłać "małego JSON-a" z
* informacją o tym ID (i innymi informacjami, które serwer chce przekazać
* klientowi) 3) tylko nagłówek Location z URL-em nowego rekordu (to zobaczymy w
* wersji JAX-RS).
*/
return product;
}
@DeleteMapping("/{id}")
public void delete(@PathVariable("id") Integer productId) {
try {
productRepository.deleteById(productId);
} catch (EmptyResultDataAccessException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Brak produktu nr " + productId);
}
}
/* Większość komunikacji w usługach REST odbywa się w formacie JSON,
* ale czasami używany jest też format XML,
* a dla niektórych danych stosujemy bezpośrednio jakiś format specjalny, np. PNG, JPG dla obrazów, PDF dla wydruków itp.
*/
@GetMapping(path="/{id}/photo", produces="image/jpeg")
public byte[] getPhoto(
@PathVariable("id") Integer productId) {
return photoUtil.readBytes(productId);
}
}
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