Commit bab03953 by Patryk Czarnik

Pobieranie kursów walut w ramach aplikacji REST

parent 0d1a2f19
...@@ -22,6 +22,8 @@ dependencies { ...@@ -22,6 +22,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'jakarta.json:jakarta.json-api:2.1.3'
runtimeOnly 'org.eclipse.parsson:jakarta.json:1.1.7'
} }
tasks.named('test') { tasks.named('test') {
......
...@@ -14,13 +14,13 @@ public class BlogController { ...@@ -14,13 +14,13 @@ public class BlogController {
private BlogService blogService; private BlogService blogService;
@GetMapping @GetMapping
public String rozmowaGet(Model model) { public String odczytajListe(Model model) {
model.addAttribute("teksty", blogService.getTeksty()); model.addAttribute("teksty", blogService.getTeksty());
return "blog.html"; return "blog.html";
} }
@PostMapping @PostMapping
public String rozmowaPost(String tekst, Model model) { public String dodajWpis(String tekst, Model model) {
if(tekst != null && !tekst.isBlank()) { if(tekst != null && !tekst.isBlank()) {
blogService.addTekst(tekst); blogService.addTekst(tekst);
} }
...@@ -28,5 +28,4 @@ public class BlogController { ...@@ -28,5 +28,4 @@ public class BlogController {
return "blog.html"; return "blog.html";
} }
} }
package org.example.demo.waluty;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDate;
import org.springframework.stereotype.Service;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import jakarta.json.JsonValue;
@Service
public class PobieranieJSON implements PobieranieWalut {
private static final String ADRES = "http://api.nbp.pl/api/exchangerates/tables";
public TabelaWalut pobierzBiezaceKursy() {
return pobierzJsonZAdresu(ADRES + "/a/?format=json");
}
public TabelaWalut pobierzArchiwalneKursy(String data) {
return pobierzJsonZAdresu(ADRES + "/a/" + data + "?format=json");
}
private TabelaWalut pobierzJsonZAdresu(String adres) {
try {
URL url = new URL(adres);
try(InputStream input = url.openStream()) {
return wczytajStream(input);
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private static TabelaWalut wczytajStream(InputStream input) {
try(JsonReader reader = Json.createReader(input)) {
// dokument zawiera [tablicę], a ta tablica zawiera {obiekt}
JsonArray array = reader.readArray();
JsonObject tabela = array.getJsonObject(0);
String typ = tabela.getString("table");
String numer = tabela.getString("no");
LocalDate data = LocalDate.parse(tabela.getString("effectiveDate"));
JsonArray waluty = tabela.getJsonArray("rates");
TabelaWalut wynikowaTabela = new TabelaWalut(typ, numer, data);
for(JsonValue jsonValue : waluty) {
JsonObject jsonObject = jsonValue.asJsonObject();
Waluta waluta = new Waluta(jsonObject.getString("code"),
jsonObject.getString("currency"),
jsonObject.getJsonNumber("mid").bigDecimalValue());
wynikowaTabela.dodajWalute(waluta);
}
return wynikowaTabela;
}
}
}
package org.example.demo.waluty;
public interface PobieranieWalut {
TabelaWalut pobierzBiezaceKursy();
TabelaWalut pobierzArchiwalneKursy(String data);
}
\ No newline at end of file
package org.example.demo.waluty;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.time.LocalDate;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/* Adnotacje @Component, @Service, @Repository, a także @Controller i @RestController
* powodują, że:
* 1) podczas startu aplikacji Spring tworzy obiekt tej klasy (pojedynczą sztukę, czyli "singleton")
* 2) ten obiekt będzie dostępny dla innych jako "komponent" ("bean") i będzie go wstrzykiwać
*
* @Primary w tym miejscu oznacza, że w razie występowania innych komponentów implementujących ten sam interfejs,
* Spring będzie preferował tę implementację.
*
*/
@Service
@Primary
public class PobieranieXML implements PobieranieWalut {
private static final String ADRES = "http://api.nbp.pl/api/exchangerates/tables";
/** Pobiera tabelę z bieżącymi kursami walut.
* Zwraca null w przypadku błędów.
*/
public TabelaWalut pobierzBiezaceKursy() {
Document doc = wczytajXmlZAdresu(ADRES + "/a?format=xml");
return tabelaZXml(doc);
}
public TabelaWalut pobierzArchiwalneKursy(String data) {
Document doc = wczytajXmlZAdresu(ADRES + "/A/" + data + "?format=xml");
return tabelaZXml(doc);
}
private Document wczytajXmlZAdresu(String adres) {
// Document Object Model
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
URL url = new URL(adres);
try (InputStream in = url.openStream()) {
return db.parse(in);
}
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
return null;
}
}
private TabelaWalut tabelaZXml(Document doc) {
if (doc == null)
return null;
try {
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
// String numer = xpath.evaluate(
// "/ArrayOfExchangeRatesTable/ExchangeRatesTable/No", doc);
String nazwaTabeli = xpath.evaluate("//Table", doc);
String numerTabeli = xpath.evaluate("//No", doc);
LocalDate data = LocalDate.parse(xpath.evaluate("//EffectiveDate", doc));
TabelaWalut tabela = new TabelaWalut(nazwaTabeli, numerTabeli, data);
NodeList rates = (NodeList) xpath.evaluate("//Rate", doc, XPathConstants.NODESET);
final int n = rates.getLength();
for (int i = 0; i < n; i++) {
Node rate = rates.item(i);
String kod = xpath.evaluate("Code", rate);
String nazwa = xpath.evaluate("Currency", rate);
BigDecimal kurs = new BigDecimal(xpath.evaluate("Mid", rate));
Waluta waluta = new Waluta(kod, nazwa, kurs);
tabela.dodajWalute(waluta);
}
return tabela;
} catch (XPathExpressionException e) {
e.printStackTrace();
return null;
}
}
}
package org.example.demo.waluty;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class TabelaWalut {
private final String typ;
private final String numer;
private final LocalDate data;
private final List<Waluta> waluty = new ArrayList<>();
public TabelaWalut(String typ, String numer, LocalDate data) {
this.typ = typ;
this.numer = numer;
this.data = data;
}
public String getTyp() {
return typ;
}
public String getNumer() {
return numer;
}
public LocalDate getData() {
return data;
}
public Collection<Waluta> getWaluty() {
return Collections.unmodifiableList(waluty);
}
@Override
public String toString() {
return "Tabela nr " + numer + " z dnia " + data + ", " + waluty.size() + " walut";
}
public void dodajWalute(Waluta waluta) {
this.waluty.add(waluta);
}
public Waluta wyszukaj(String kod) {
for(Waluta waluta : waluty) {
if(waluta.getKod().equals(kod)) {
return waluta;
}
}
return null;
}
public String[] getKodyWalut() {
return this.waluty.stream()
.map(Waluta::getKod)
.sorted()
.toArray(String[]::new);
}
}
package org.example.demo.waluty;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Waluta {
private final String kod;
private final String nazwa;
private final BigDecimal kurs;
public Waluta(String kod, String nazwa, BigDecimal kurs) {
this.kod = kod;
this.nazwa = nazwa;
this.kurs = kurs;
}
public Waluta(String kod, String nazwa, String kurs) {
this(kod, nazwa, new BigDecimal(kurs));
}
public String getKod() {
return kod;
}
public String getNazwa() {
return nazwa;
}
public BigDecimal getKurs() {
return kurs;
}
@Override
public String toString() {
return "Waluta [kod=" + kod + ", nazwa=" + nazwa + ", kurs=" + kurs + "]";
}
public BigDecimal przeliczNaZlote(BigDecimal kwota) {
return kwota.multiply(kurs).setScale(2, RoundingMode.HALF_EVEN);
}
public BigDecimal przeliczNaWalute(BigDecimal kwota) {
return kwota.divide(kurs, 2, RoundingMode.HALF_EVEN);
}
}
package org.example.demo.waluty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WalutyController {
/* @Autowired oznacza, że Spring wstawi do tej zmiennej referencję do obiektu tego typu.
* Gdyby nie potrafił tego zrobić, to aplikacja się nie uruchomi. Pole nie zostanie pozostawione z nullem.
*
* To się nazywa "wstrzykiwanie zależności" / dependency injection.
* Są 3 sposoby wstrzykiwania , o 2 pozostałych później.
* Wstrzykiwanie zal. działa tylko gdy obiekt tej klasy jest inicjalizowany przez Springa.
*
* Wstrzykiwać można obiekty, które są typu:
* - komponent (bean) istniejący w tej samej aplikacji (tak jest tutaj)
* - klasa o specjalnym znaczeniu, którą Spring "zna", np. ServletContext
*
* W miejscu użycia zmienna może być typu interfejsowego,
* a Spring wstawi "jakąś implementację" tego interfejsu,
* jeśli taka implementacja jest dostępna wśród komponentów (beanów).
*/
@Autowired
private PobieranieWalut pobieracz;
@GetMapping("/waluty")
public String wyswietlWaluty(String data, Model model) {
TabelaWalut tabela;
if(data == null) {
tabela = pobieracz.pobierzBiezaceKursy();
} else {
tabela = pobieracz.pobierzArchiwalneKursy(data);
}
model.addAttribute("tabela", tabela);
return "waluty.html";
}
}
package org.example.demo.waluty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/waluty.rest")
public class WalutyRest {
@Autowired
private PobieranieWalut pobieracz;
@GetMapping
public TabelaWalut wyswietlWaluty() {
TabelaWalut tabela = pobieracz.pobierzBiezaceKursy();
return tabela;
}
@GetMapping("/{data}")
public TabelaWalut wyswietlWaluty(@PathVariable String data) {
TabelaWalut tabela = pobieracz.pobierzArchiwalneKursy(data);
return tabela;
}
}
...@@ -48,5 +48,12 @@ ...@@ -48,5 +48,12 @@
<li><a th:href="@{/blog.json}">RESTController</a> podgląd w wersji JSON</li> <li><a th:href="@{/blog.json}">RESTController</a> podgląd w wersji JSON</li>
</ul> </ul>
<h2>Waluty</h2>
<ul>
<li><a th:href="@{/waluty}">Kursy walut</a></li>
<li><a th:href="@{/waluty.rest}">Waluty REST</a> - bieżące</li>
<li><a th:href="@{/waluty.rest/2008-08-08}">Waluty REST</a> - archiwalne</li>
</ul>
</body> </body>
</html> </html>
<!DOCTYPE html>
<html lang="pl" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Pobieraczka kursów walutowych</title>
<link rel="stylesheet" type="text/css" th:href="@{/styl.css}" href="../static/styl.css">
</head>
<body>
<h1>Kursy walut</h1>
<form>
<div><label for="data">Wybierz datę:</label>
<input id="data" type="date" name="data" th:value="${param.data}">
</div>
<button>Pobierz</button>
</form>
<div th:if="${tabela != null}">
<h3>Pobrane dane</h3>
<div>Numer tabeli: <span th:text="${tabela.numer}">1234/2023</span></div>
<div>Data: <span th:text="${tabela.data}">2023-05-04</span></div>
<table class="tabela-walut">
<tr><th>Kod</th><th>Nazwa</th><th>Kurs</th></tr>
<tr th:each="w : ${tabela.waluty}">
<td th:text="${w.kod}">EUR</td>
<td th:text="${w.nazwa}">euro</td>
<td th:text="${w.kurs}">4.5432</td>
</tr>
</table>
</div>
</body>
</html>
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