Commit 1cf2e1b0 by Patryk Czarnik

Przykład pobieranie kursów walutowych

parent 1ae1f2f0
...@@ -23,6 +23,8 @@ dependencies { ...@@ -23,6 +23,8 @@ dependencies {
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 'org.apache.commons:commons-lang3' implementation 'org.apache.commons:commons-lang3'
implementation 'jakarta.json:jakarta.json-api:2.1.2'
runtimeOnly 'org.eclipse.parsson:jakarta.json:1.1.3'
} }
tasks.named('test') { tasks.named('test') {
......
package alx.aplikacja.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 alx.aplikacja.waluty;
public interface PobieranieWalut {
TabelaWalut pobierzBiezaceKursy();
TabelaWalut pobierzArchiwalneKursy(String data);
}
\ No newline at end of file
package alx.aplikacja.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 alx.aplikacja.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 alx.aplikacja.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 alx.aplikacja.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 alx.aplikacja.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;
}
}
...@@ -35,3 +35,20 @@ form { ...@@ -35,3 +35,20 @@ form {
font-weight: bold; font-weight: bold;
} }
table.tabela-walut {
background-color: #FFFFFF;
border-collapse: collapse;
border: 3px solid black;
margin: 0 auto;
}
table.tabela-walut td, table.tabela-walut th {
border: 1px solid black;
padding: 2px;
}
table.tabela-walut th {
background-color: #FFCCDD;
border-bottom: 3px double black;
}
...@@ -47,5 +47,12 @@ ...@@ -47,5 +47,12 @@
<li><a th:href="@{/blog}">Formularz</a> do wpisywania tekstów</li> <li><a th:href="@{/blog}">Formularz</a> do wpisywania tekstów</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