diff --git a/.travis.yml b/.travis.yml index 1103c8581..efa218ffb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,4 @@ env: - secure: Bm0zIwBo4saIBlfr9YDvNqd8UN50FeT2hZyum3RFDmJqaXiUqwxw7sCz/lRjUwTfQrvGJXQ1I8le7zE4OVQdvpn4/LwwTJIjLY2jZNjzs4mZhkHzsM5IcGcL3lukR6soVYrGloQwmw63Okw2kZxces+1fveisPIKDlVaU1RTtMQ= after_success: - - "[[ $TRAVIS_BRANCH == \"develop\" ]] && { python addServer.py; mvn clean deploy -DskipTests=true --settings ~/.m2/mySettings.xml; };" + - "[[ $TRAVIS_BRANCH == \"develop\" ]] && { python addServer.py; mvn clean deploy -pl 'mvvmfx,mvvmfx-cdi,mvvmfx-guice,mvvmfx-archetype' -am -DskipTests=true --settings ~/.m2/mySettings.xml; };" diff --git a/README.md b/README.md index 97711482c..4b2880dcd 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,39 @@ __mvvm(fx)__ is an application framework which provides you necessary components __MVVM__ is the enhanced version of the [Presentation Model](http://martinfowler.com/eaaDev/PresentationModel.html "Presentation Model") pattern and was created by Microsoft engineers for [WPF](http://msdn.microsoft.com/en-us/library/ms754130.aspx "WPF") . JavaFX and WPF does have similarities like Databinding and descriptive UI declaration (FXML/XAML). Because of this fact we aptopt best practices of the development with the Microsoft technology. +[![Build Status](https://travis-ci.org/sialcasa/mvvmFX.svg?branch=develop)](https://travis-ci.org/sialcasa/mvvmFX) + ###[Howto](../../wiki "Howto")### -###Maven dependency### +### Maven dependency### + +#### Stable Release +``` + + de.saxsys + mvvmfx + 1.0.0 + +``` +#### Development Snapshot ``` de.saxsys - mvvmFX - 0.4.0 + mvvmfx + 1.1.0-SNAPSHOT ``` + ### Get Help If you need help you can use the forums on [Google Groups](https://groups.google.com/forum/#!forum/mvvmfx-dev) for asking questions and interacting with the mvvmFX developers. Additionally you can create issues, report bugs and add feature requests on the issue tracker at [github](https://github.com/sialcasa/mvvmFX/issues). ### Links -[javadoc mvvmfx core](http://sialcasa.github.io/mvvmFX/javadoc/0.4.0/mvvmfx/) +- [Project Page](http://sialcasa.github.io/mvvmFX/) +- [javadoc mvvmfx core](http://sialcasa.github.io/mvvmFX/javadoc/1.0.0/mvvmfx/) +- [javadoc mvvmfx-cdi](http://sialcasa.github.io/mvvmFX/javadoc/1.0.0/mvvmfx-cdi/) +- [javadoc mvvmfx-guice](http://sialcasa.github.io/mvvmFX/javadoc/1.0.0/mvvmfx-guice/) -[javadoc mvvmfx-cdi](http://sialcasa.github.io/mvvmFX/javadoc/0.4.0/mvvmfx-cdi/) - -[javadoc mvvmfx-guice](http://sialcasa.github.io/mvvmFX/javadoc/0.4.0/mvvmfx-guice/) - -[![Build Status](https://travis-ci.org/sialcasa/mvvmFX.svg?branch=develop)](https://travis-ci.org/sialcasa/mvvmFX) diff --git a/deploy_release.sh b/deploy_release.sh new file mode 100755 index 000000000..4a16a4e56 --- /dev/null +++ b/deploy_release.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# Script for the deployment of releases to maven central +# +# Expecting a "settings-mvvmfx.xml" file in maven_home with the authentication configuration for the central repository. +# +# The settings file should look something like this: +# +# +# +# +# +# sonatype-nexus-snapshots +# YOUR USERNAME +# YOUR PASSWORD +# +# +# sonatype-nexus-staging +# YOUR USERNAME +# YOUR PASSWORD +# +# +# + +mvn clean deploy -pl 'mvvmfx,mvvmfx-cdi,mvvmfx-guice,mvvmfx-archetype' -am -DskipTests=true -Pdeploy-release --settings ~/.m2/settings-mvvmfx.xml diff --git a/examples/README.md b/examples/README.md index ed4bfc10e..91e20c9b9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,3 +11,7 @@ as dependency injection framework. - **mvvmfx-fx-root-example**: contains a small custom control that uses the fx:root element together with mvvmfx. - **mvvmfx-helloworld-example**: A simple hello world view. This example is used in the [Getting Started/Step-by-Step tutorial](/../../wiki/Getting-Started-HelloWorld-%28deutsch%29). - **mvvmfx-helloworld-without-fxml**: A hello world example that shows hot to use MvvmFX with a view implemented in pure Java and not with FXML. +- **mvvmfx-contacts**: A contact management application. This example shows a master-detail view, dialogs and the usage of CDI including CDI-Events. +This example also integrates some other JavaFX community libraries. +- **mvvmfx-synchronizefx**: This example uses the library [SynchronizeFX](https://github.com/saxsys/SynchronizeFX) to create a distributed ViewModel. +This way the state of the UI of different instances of the App (on different JVM's, on different computers) is always synchronized between the apps. \ No newline at end of file diff --git a/examples/mvvmfx-books-example/README.md b/examples/mvvmfx-books-example/README.md new file mode 100644 index 000000000..369cae281 --- /dev/null +++ b/examples/mvvmfx-books-example/README.md @@ -0,0 +1,24 @@ +# MvvmFX Books Example + +This example app is a client for a library REST service. You can search for books and view the +details of found books in a master-detail view. + +![screenshot](screenshot.png) + +This example was used for [a talk](http://www.jug-gr.de/2014/12/03/model-view-star.html) at the +[JavaUserGroup Görlitz](http://www.jug-gr.de) on [UI-Design patterns](https://github.com/lestard/model-view-star). + +Originally [this app](https://github.com/sbley/hypermedia-library-client/tree/javafx) was created by [Stefan Bley](https://github.com/sbley) and +[Alexander Casall](https://github.com/sialcasa) for a talk on Hypermedia-APIs. +To run the app you need to have the [Hypermedia-API server](https://github.com/sbley/hypermedia-library-server) +up and running on `localhost` so that the client can make the necessary REST-Requests. + + +The app uses the following libraries (among others): + +- [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx) // Icons +- [FlatterFX](http://www.guigarage.com/javafx-themes/flatter/) // Styling +- [Advanced-Bindings](https://github.com/lestard/advanced-bindings) // Binding-Utils +- [EasyDI](https://github.com/lestard/EasyDI) // Dependency-Injection +- [AssertJ-JavaFX](https://github.com/lestard/assertj-javafx) // Testing +- [HALBuilder](https://github.com/HalBuilder) // REST-Client \ No newline at end of file diff --git a/examples/mvvmfx-books-example/pom.xml b/examples/mvvmfx-books-example/pom.xml new file mode 100644 index 000000000..b2e6b008a --- /dev/null +++ b/examples/mvvmfx-books-example/pom.xml @@ -0,0 +1,102 @@ + + + + mvvmfx-examples + de.saxsys + 1.0.0 + + 4.0.0 + + mvvmfx-library-example + + + UTF-8 + 1.8 + 1.8 + + + + + de.saxsys + mvvmfx + ${project.parent.version} + + + eu.lestard + easy-di + 0.1.0 + + + de.jensd + fontawesomefx + 8.0.10 + + + com.guigarage + flatter + 0.7 + + + eu.lestard + advanced-bindings + 0.2.0 + + + + + com.theoryinpractise + halbuilder-json + 4.0.2 + + + com.theoryinpractise + halbuilder-jaxrs + 1.1.1 + + + org.jboss.resteasy + resteasy-client + 3.0.9.Final + + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + 1.1.2 + + + + + + junit + junit + test + + + + org.assertj + assertj-core + test + + + eu.lestard + assertj-javafx + 0.2.0 + test + + + org.mockito + mockito-all + test + + + + + \ No newline at end of file diff --git a/examples/mvvmfx-books-example/screenshot.png b/examples/mvvmfx-books-example/screenshot.png new file mode 100644 index 000000000..b38fad33d Binary files /dev/null and b/examples/mvvmfx-books-example/screenshot.png differ diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java new file mode 100644 index 000000000..8e03ea484 --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java @@ -0,0 +1,40 @@ +package de.saxsys.mvvmfx.examples.books; + +import com.guigarage.flatterfx.FlatterFX; +import com.guigarage.flatterfx.FlatterInputType; +import de.saxsys.mvvmfx.FluentViewLoader; +import de.saxsys.mvvmfx.MvvmFX; +import de.saxsys.mvvmfx.examples.books.backend.LibraryService; +import de.saxsys.mvvmfx.examples.books.backend.LibraryServiceImpl; +import eu.lestard.easydi.EasyDI; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +public class App extends Application { + + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { + EasyDI context = new EasyDI(); + context.bindInterface(LibraryService.class, LibraryServiceImpl.class); + MvvmFX.setCustomDependencyInjector(context::getInstance); + + primaryStage.setTitle("Library JavaFX"); + primaryStage.setMinWidth(1200); + primaryStage.setMaxWidth(1200); + primaryStage.setMinHeight(700); + + Scene scene = new Scene(FluentViewLoader.fxmlView(MainView.class).load().getView(), 1200, 700); + + scene.setFill(Color.TRANSPARENT); + primaryStage.setScene(scene); + primaryStage.show(); + FlatterFX.style(FlatterInputType.DEFAULT); + } +} + diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/BookViewModel.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/BookViewModel.java new file mode 100644 index 000000000..7d25474ef --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/BookViewModel.java @@ -0,0 +1,30 @@ +package de.saxsys.mvvmfx.examples.books; + + +import de.saxsys.mvvmfx.examples.books.backend.Book; + +public class BookViewModel { + + private final Book book; + + public BookViewModel(Book book){ + this.book = book; + } + + public String getTitle(){ + return book.getTitle(); + } + + public String getAuthor(){ + return book.getAuthor(); + } + + public String getDescription(){ + return book.getDesc(); + } + + @Override + public String toString() { + return getTitle(); + } +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainView.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainView.java new file mode 100644 index 000000000..bc0a0dd6d --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainView.java @@ -0,0 +1,53 @@ +package de.saxsys.mvvmfx.examples.books; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.TextField; + +public class MainView implements FxmlView { + + + @FXML + private Label titleLabel; + + @FXML + private Label authorLabel; + + @FXML + private TextField searchTextField; + + @FXML + private Button searchButton; + + @FXML + private Label descriptionLabel; + + @FXML + private ListView bookList; + + @FXML + private Label errorLabel; + + @InjectViewModel + private MainViewModel viewModel; + + public void initialize(){ + searchTextField.textProperty().bindBidirectional(viewModel.searchStringProperty()); + titleLabel.textProperty().bind(viewModel.bookTitleProperty()); + authorLabel.textProperty().bind(viewModel.bookAuthorProperty()); + descriptionLabel.textProperty().bind(viewModel.bookDescriptionProperty()); + + bookList.setItems(viewModel.booksProperty()); + + viewModel.selectedBookProperty().bind(bookList.getSelectionModel().selectedItemProperty()); + errorLabel.textProperty().bind(viewModel.errorProperty()); + } + + public void searchButtonPressed() { + viewModel.search(); + } +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainViewModel.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainViewModel.java new file mode 100644 index 000000000..37c177d6a --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainViewModel.java @@ -0,0 +1,86 @@ +package de.saxsys.mvvmfx.examples.books; + +import de.saxsys.mvvmfx.ViewModel; +import de.saxsys.mvvmfx.examples.books.backend.Book; +import de.saxsys.mvvmfx.examples.books.backend.Error; +import de.saxsys.mvvmfx.examples.books.backend.LibraryService; +import eu.lestard.advanced_bindings.api.ObjectBindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import javax.inject.Singleton; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@Singleton +public class MainViewModel implements ViewModel { + + private final LibraryService libraryService; + private StringProperty searchString = new SimpleStringProperty(""); + + private StringProperty bookTitle = new SimpleStringProperty(); + private StringProperty bookAuthor = new SimpleStringProperty(); + private StringProperty bookDescription = new SimpleStringProperty(); + + private ObservableList books = FXCollections.observableArrayList(); + + private ObjectProperty selectedBook = new SimpleObjectProperty<>(); + + private StringProperty error = new SimpleStringProperty(); + + public MainViewModel(LibraryService libraryService){ + this.libraryService = libraryService; + + bookTitle.bind(ObjectBindings.map(selectedBook, BookViewModel::getTitle)); + bookAuthor.bind(ObjectBindings.map(selectedBook, BookViewModel::getAuthor)); + bookDescription.bind(ObjectBindings.map(selectedBook, BookViewModel::getDescription)); + } + + + public void search(){ + Consumer errorHandler = err -> error.set(err.getMessage()); + + final List result = libraryService.search(searchString.get(), errorHandler); + + books.clear(); + books.addAll(result + .stream() + .map(bookWithoutDescription -> libraryService.showDetails(bookWithoutDescription, errorHandler)) + .map(BookViewModel::new) + .collect(Collectors.toList())); + } + + + public StringProperty searchStringProperty() { + return searchString; + } + + public StringProperty bookTitleProperty() { + return bookTitle; + } + + public StringProperty bookAuthorProperty() { + return bookAuthor; + } + + public StringProperty bookDescriptionProperty() { + return bookDescription; + } + + public ObservableList booksProperty(){ + return books; + } + + public ObjectProperty selectedBookProperty(){ + return selectedBook; + } + + public StringProperty errorProperty(){ + return error; + } +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/Book.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/Book.java new file mode 100644 index 000000000..564b0eeda --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/Book.java @@ -0,0 +1,76 @@ +package de.saxsys.mvvmfx.examples.books.backend; + +import com.theoryinpractise.halbuilder.api.Link; + +public class Book { + + private final String href; + private final String title; + private String author; + private String desc; + private Integer borrower; + private final Link relLend; + private final Link relReturn; + + public Book(String href, String title, String author, String desc, Link relLend, Link relReturn) { + super(); + this.href = href; + this.title = title; + this.author = author; + this.desc = desc; + this.relLend = relLend; + this.relReturn = relReturn; + } + + public String getHref() { + return href; + } + + public String getTitle() { + return title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Integer getBorrower() { + return borrower; + } + + public void setBorrower(Integer borrower) { + this.borrower = borrower; + } + + public Link getRelLend() { + return relLend; + } + + public Link getRelReturn() { + return relReturn; + } + + public boolean isLent() { + return borrower != null; + } + + public boolean isAvailable() { + return null != relLend; + } + + public boolean isReturnable() { + return null != relReturn; + } +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/Error.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/Error.java new file mode 100644 index 000000000..1dd344300 --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/Error.java @@ -0,0 +1,25 @@ +package de.saxsys.mvvmfx.examples.books.backend; + +public class Error { + + private final String message; + + private final String details; + + public static Error error(String message, String details){ + return new Error(message, details); + } + + private Error(String message, String details){ + this.message = message; + this.details = details; + } + + public String getMessage() { + return message; + } + + public String getDetails() { + return details; + } +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/HalUtil.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/HalUtil.java new file mode 100644 index 000000000..fed74d273 --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/HalUtil.java @@ -0,0 +1,16 @@ +package de.saxsys.mvvmfx.examples.books.backend; + +public class HalUtil { + + public static String replaceParam(String href, String param) { + return href.replaceFirst("\\{.+\\}", param); + } + + public static Integer toInt(Object value) { + if (value.getClass().isAssignableFrom(String.class)) { + String valueStr = (String) value; + return Integer.valueOf(valueStr); + } + return null; + } +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/LibraryService.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/LibraryService.java new file mode 100644 index 000000000..f3ff47e79 --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/LibraryService.java @@ -0,0 +1,14 @@ +package de.saxsys.mvvmfx.examples.books.backend; + +import java.util.List; +import java.util.function.Consumer; + +public interface LibraryService { + List search(String query, Consumer errorCallback); + + Book showDetails(Book book, Consumer errorCallback); + + Book lend(String lendTo, Book detailBook, Consumer errorCallback); + + Book takeBack(Book detailBook, Consumer errorCallback); +} diff --git a/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/LibraryServiceImpl.java b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/LibraryServiceImpl.java new file mode 100644 index 000000000..87b59087c --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/java/de/saxsys/mvvmfx/examples/books/backend/LibraryServiceImpl.java @@ -0,0 +1,156 @@ +package de.saxsys.mvvmfx.examples.books.backend; + +import com.theoryinpractise.halbuilder.api.ContentRepresentation; +import com.theoryinpractise.halbuilder.api.Link; +import com.theoryinpractise.halbuilder.api.ReadableRepresentation; +import com.theoryinpractise.halbuilder.jaxrs.JaxRsHalBuilderReaderSupport; +import org.jboss.resteasy.client.jaxrs.cache.BrowserCacheFeature; +import org.jboss.resteasy.plugins.interceptors.encoding.AcceptEncodingGZIPFilter; +import org.jboss.resteasy.plugins.interceptors.encoding.GZIPDecodingInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Response; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +import static com.theoryinpractise.halbuilder.api.RepresentationFactory.*; + +public class LibraryServiceImpl implements Serializable, LibraryService { + + private static final long serialVersionUID = 1L; + private static final String BASE_URL = "http://localhost:8080/rest"; + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryServiceImpl.class); + + private Client apiClient; + + public LibraryServiceImpl(){ + apiClient = + ClientBuilder.newClient() + .register(JaxRsHalBuilderReaderSupport.class) + .register(BrowserCacheFeature.class) + .register(GZIPDecodingInterceptor.class) + .register(AcceptEncodingGZIPFilter.class); + } + + @Override + public List search(String query, Consumer errorCallback) { + try { + // home + Response responseHome = apiClient.target(BASE_URL).request(HAL_JSON).get(); + ContentRepresentation repHome = responseHome.readEntity(ContentRepresentation.class); + // search + Link searchLink = repHome.getLinkByRel("lib:search"); + String href = HalUtil.replaceParam(searchLink.getHref(), query); + Response responseSearch = apiClient.target(href).request(HAL_JSON).get(); + ContentRepresentation repSearch = + responseSearch.readEntity(ContentRepresentation.class); + Collection resultSet = + repSearch.getResourceMap().get("lib:book"); + List books = new ArrayList<>(); + if (null != resultSet) { + for (ReadableRepresentation rep : resultSet) { + Book book = toBook(rep); + if (null == book.getTitle() || null == book.getAuthor()) { + // get book + Response response = + apiClient.target(book.getHref()).request(HAL_JSON).get(); + ContentRepresentation repBook = + response.readEntity(ContentRepresentation.class); + book = toBook(repBook); + } + books.add(book); + } + } + return books; + } catch (Throwable e) { + LOGGER.error("Error during search", e); + errorCallback.accept(Error.error("Error during search", e.getMessage())); + return new ArrayList<>(); + } + } + + @Override + public Book showDetails(Book book, Consumer errorCallback) { + LOGGER.debug("Show details for book at {}", book.getHref()); + try { + Response response = apiClient.target(book.getHref()).request(HAL_JSON).get(); + ContentRepresentation rep = response.readEntity(ContentRepresentation.class); + return toBook(rep); + } catch (Exception e) { + LOGGER.error("Error retrieving book", e); + errorCallback.accept(Error.error("Error retrieving book", e.getMessage())); + return null; + } + } + + @Override + public Book lend(String lendTo, Book detailBook, Consumer errorCallback) { + LOGGER.debug("Lend book {} to member {}", detailBook.getTitle(), lendTo); + try { + Integer.valueOf(lendTo); + } catch (NumberFormatException e) { + errorCallback.accept(Error.error("Invalid MemberID", "MemberID is not a number")); + return null; + } + Response response = + apiClient.target(detailBook.getRelLend().getHref()) + .request(HAL_JSON) + .put(Entity.json("{\"memberId\":" + lendTo + "}")); + ContentRepresentation rep = response.readEntity(ContentRepresentation.class); + if (response.getStatus() >= 400) { + String message = (String) rep.getValue("title"); + String detail = (String) rep.getValue("detail", null); + LOGGER.error("{} {} {}", response.getStatus(), message, detail); + errorCallback.accept(Error.error(message, detail)); + return null; + } else { + detailBook = toBook(rep); + LOGGER.debug("Book {} lent to member {}", detailBook.getTitle(), + detailBook.getBorrower()); + return detailBook; + } + } + + @Override + public Book takeBack(Book detailBook, Consumer errorCallback) { + LOGGER.debug("Return book {} from member {}", detailBook.getTitle(), + detailBook.getBorrower()); + Response response = + apiClient.target( + HalUtil.replaceParam(detailBook.getRelReturn().getHref(), detailBook.getBorrower() + .toString())) + .request(HAL_JSON) + .delete(); + ContentRepresentation rep = response.readEntity(ContentRepresentation.class); + if (response.getStatus() >= 400) { + String message = (String) rep.getValue("title"); + String detail = (String) rep.getValue("detail", null); + LOGGER.error("{} {}", message, detail); + errorCallback.accept(Error.error(message, detail)); + return null; + } else { + LOGGER.debug("Book {} returned", detailBook.getTitle()); + return toBook(rep); + } + } + + private Book toBook(ReadableRepresentation rep) { + Book book = + new Book( + rep.getResourceLink().getHref(), (String) rep.getValue("title", null), + (String) rep.getValue("author", null), (String) rep.getValue("description", + null), rep.getLinkByRel("lib:lend"), rep.getLinkByRel("lib:return")); + // add borrower if available + List borrowerResource = rep.getResourcesByRel("borrower"); + if (1 == borrowerResource.size()) + book.setBorrower(Integer.valueOf((String) borrowerResource.get(0).getValue("id"))); + return book; + } +} diff --git a/examples/mvvmfx-books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/MainView.fxml b/examples/mvvmfx-books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/MainView.fxml new file mode 100644 index 000000000..a36d0e1b6 --- /dev/null +++ b/examples/mvvmfx-books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/MainView.fxml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + +