refactor: Refactored Layers Architecture To Be A Spring Boot Application. Issue #2450 (#2469)

* #2450 1. Refactored Layers Architecture to be a Spring boot app. 2. Changed the xml-based configuration to annotation-based. 3. Used spring's constructor injection to pass around needed objects. 4. Implemented deleteAll() methods for the CakeBakingServiceImpl to be used during testing  5. Implemented a CommandLineRunner to run the spring boot app. And others.

* #2450 added the contents of the etc directory and a README.md

* #2450 made corrections in response to the PR tests

* #2450 made corrections in response to requested changes

* #2450 made corrections in response to requested changes

---------

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
This commit is contained in:
JabezBrew
2023-07-11 09:57:16 +00:00
committed by GitHub
parent e40923d938
commit edbb59a982
34 changed files with 1023 additions and 1199 deletions
+9 -27
View File
@@ -3,34 +3,31 @@ title: Layers
category: Architectural
language: en
tag:
- Decoupling
- Decoupling
---
## Intent
Layers is an architectural pattern where software responsibilities are divided among the different
Layers is an architectural pattern where software responsibilities are divided among the different
layers of the application.
## Explanation
Real world example
> Consider a web site displaying decorated cakes for weddings and such. Instead of the web page
> directly reaching into the database, it relies on a service to deliver this information. The
> Consider a website displaying decorated cakes for weddings and such. Instead of the web page
> directly reaching into the database, it relies on a service to deliver this information. The
> service then queries the data layer to assimilate the needed information.
In plain words
> With Layers architectural pattern different concerns reside on separate layers. View layer is
> interested only in rendering, service layer assembles the requested data from various sources, and
> With Layers architectural pattern different concerns reside on separate layers. View layer is
> interested only in rendering, service layer assembles the requested data from various sources, and
> data layer gets the bits from the data storage.
Wikipedia says
> In software engineering, multitier architecture (often referred to as n-tier architecture) or
> multilayered architecture is a clientserver architecture in which presentation, application
> In software engineering, multitier architecture (often referred to as n-tier architecture) or
> multilayered architecture is a clientserver architecture in which presentation, application
> processing, and data management functions are physically separated.
**Programmatic Example**
On the data layer, we keep our cake building blocks. `Cake` consist of layers and topping.
@@ -38,14 +35,11 @@ On the data layer, we keep our cake building blocks. `Cake` consist of layers an
```java
@Entity
public class Cake {
@Id
@GeneratedValue
private Long id;
@OneToOne(cascade = CascadeType.REMOVE)
private CakeTopping topping;
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<CakeLayer> layers;
}
@@ -55,17 +49,11 @@ The service layer offers `CakeBakingService` for easy access to different aspect
```java
public interface CakeBakingService {
void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException;
List<CakeInfo> getAllCakes();
void saveNewTopping(CakeToppingInfo toppingInfo);
List<CakeToppingInfo> getAvailableToppings();
void saveNewLayer(CakeLayerInfo layerInfo);
List<CakeLayerInfo> getAvailableLayers();
}
```
@@ -74,20 +62,14 @@ On the top we have our `View` responsible of rendering the cakes.
```java
public interface View {
void render();
}
@Slf4j
public class CakeViewImpl implements View {
private final CakeBakingService cakeBakingService;
public CakeViewImpl(CakeBakingService cakeBakingService) {
this.cakeBakingService = cakeBakingService;
}
public void render() {
cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString()));
}
@@ -108,4 +90,4 @@ Use the Layers architecture when
## Credits
* [Pattern Oriented Software Architecture Volume 1: A System of Patterns](https://www.amazon.com/gp/product/0471958697/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0471958697&linkCode=as2&tag=javadesignpat-20&linkId=e3f42d7a2a4cc8c619bbc0136b20dadb)
* [Pattern Oriented Software Architecture Volume 1: A System of Patterns](https://www.amazon.com/gp/product/0471958697/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0471958697&linkCode=as2&tag=javadesignpat-20&linkId=e3f42d7a2a4cc8c619bbc0136b20dadb)
+239 -239
View File
@@ -1,256 +1,256 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<interface id="1" language="java" name="com.iluwatar.layers.dao.CakeDao" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="289" y="916"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
file="/layers/src/main/java/com/iluwatar/layers/CakeDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="289" y="916"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="2" language="java" name="com.iluwatar.layers.entity.CakeLayer" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeLayer.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1438" y="826"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeLayer.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1438" y="826"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="com.iluwatar.layers.view.CakeViewImpl" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeViewImpl.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="221"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeViewImpl.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="221"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.layers.exception.CakeBakingException" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeBakingException.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="143" y="502"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeBakingException.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="143" y="502"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.layers.service.CakeBakingServiceImpl" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="694"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="694"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="6" language="java" name="com.iluwatar.layers.dao.CakeLayerDao" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeLayerDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="918"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
file="/layers/src/main/java/com/iluwatar/layers/CakeLayerDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="918"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<interface id="7" language="java" name="com.iluwatar.layers.view.View" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/View.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="65"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
file="/layers/src/main/java/com/iluwatar/layers/View.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="65"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="8" language="java" name="com.iluwatar.layers.dto.CakeToppingInfo" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="817" y="530"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="817" y="530"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="9" language="java" name="com.iluwatar.layers.dto.CakeInfo" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeInfo.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="883" y="265"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeInfo.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="883" y="265"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<interface id="10" language="java" name="com.iluwatar.layers.dao.CakeToppingDao" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeToppingDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="633" y="918"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
file="/layers/src/main/java/com/iluwatar/layers/CakeToppingDao.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="633" y="918"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<interface id="11" language="java" name="com.iluwatar.layers.service.CakeBakingService" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="431"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
file="/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="456" y="431"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="12" language="java" name="com.iluwatar.layers.dto.CakeLayerInfo" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1055" y="530"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1055" y="530"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="13" language="java" name="com.iluwatar.layers.entity.Cake" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/Cake.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1160" y="826"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
file="/layers/src/main/java/com/iluwatar/layers/Cake.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1160" y="826"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="14" language="java" name="com.iluwatar.layers.entity.CakeTopping" project="layers"
file="/layers/src/main/java/com/iluwatar/layers/CakeTopping.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="876" y="826"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<association id="15">
<end type="SOURCE" refId="9" navigable="false">
<attribute id="16" name="cakeToppingInfo"/>
<multiplicity id="17" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="8" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="18">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="10"/>
</dependency>
<association id="19">
<end type="SOURCE" refId="13" navigable="false">
<attribute id="20" name="layers"/>
<multiplicity id="21" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="2" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="22">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="4"/>
</dependency>
<realization id="23">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="11"/>
</realization>
<association id="24">
<end type="SOURCE" refId="13" navigable="false">
<attribute id="25" name="topping"/>
<multiplicity id="26" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="14" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="27">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="28" name="cake"/>
<multiplicity id="29" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="13" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="30">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="14"/>
</dependency>
<dependency id="31">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="12"/>
</dependency>
<association id="32">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="33" name="cakeBakingService"/>
<multiplicity id="34" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="11" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="35">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="1"/>
</dependency>
<dependency id="36">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="2"/>
</dependency>
<dependency id="37">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</dependency>
<dependency id="38">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="8"/>
</dependency>
<association id="39">
<end type="SOURCE" refId="9" navigable="false">
<attribute id="40" name="cakeLayerInfos"/>
<multiplicity id="41" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="12" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="42">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="7"/>
</realization>
<dependency id="43">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="12"/>
</dependency>
<dependency id="44">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="9"/>
</dependency>
<association id="45">
<end type="SOURCE" refId="14" navigable="false">
<attribute id="46" name="cake"/>
<multiplicity id="47" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="13" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="48">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="6"/>
</dependency>
<dependency id="49">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="8"/>
</dependency>
<dependency id="50">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="9"/>
</dependency>
<dependency id="51">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="13"/>
</dependency>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="true" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
file="/layers/src/main/java/com/iluwatar/layers/CakeTopping.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="876" y="826"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<association id="15">
<end type="SOURCE" refId="9" navigable="false">
<attribute id="16" name="cakeToppingInfo"/>
<multiplicity id="17" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="8" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="18">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="10"/>
</dependency>
<association id="19">
<end type="SOURCE" refId="13" navigable="false">
<attribute id="20" name="layers"/>
<multiplicity id="21" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="2" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="22">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="4"/>
</dependency>
<realization id="23">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="11"/>
</realization>
<association id="24">
<end type="SOURCE" refId="13" navigable="false">
<attribute id="25" name="topping"/>
<multiplicity id="26" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="14" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="27">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="28" name="cake"/>
<multiplicity id="29" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="13" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="30">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="14"/>
</dependency>
<dependency id="31">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="12"/>
</dependency>
<association id="32">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="33" name="cakeBakingService"/>
<multiplicity id="34" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="11" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="35">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="1"/>
</dependency>
<dependency id="36">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="2"/>
</dependency>
<dependency id="37">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</dependency>
<dependency id="38">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="8"/>
</dependency>
<association id="39">
<end type="SOURCE" refId="9" navigable="false">
<attribute id="40" name="cakeLayerInfos"/>
<multiplicity id="41" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="12" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="42">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="7"/>
</realization>
<dependency id="43">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="12"/>
</dependency>
<dependency id="44">
<end type="SOURCE" refId="11"/>
<end type="TARGET" refId="9"/>
</dependency>
<association id="45">
<end type="SOURCE" refId="14" navigable="false">
<attribute id="46" name="cake"/>
<multiplicity id="47" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="13" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<dependency id="48">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="6"/>
</dependency>
<dependency id="49">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="8"/>
</dependency>
<dependency id="50">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="9"/>
</dependency>
<dependency id="51">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="13"/>
</dependency>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="true" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>
+2 -2
View File
@@ -131,6 +131,6 @@ CakeViewImpl --> "-cakeBakingService" CakeBakingService
App --> "-cakeBakingService" CakeBakingService
Cake --> "-topping" CakeTopping
CakeLayer --> "-cake" Cake
CakeBakingServiceImpl ..|> CakeBakingService
CakeViewImpl ..|> View
CakeBakingServiceImpl ..|> CakeBakingService
CakeViewImpl ..|> View
@enduml
+57 -64
View File
@@ -1,11 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
The MIT License
Copyright © 2014-2022 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
@@ -22,67 +19,63 @@
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.-->
-->
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<groupId>com.iluwatar.layers</groupId>
<artifactId>layers</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.layers.app.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<artifactId>layers</artifactId>
<name>layers</name>
<description>layers</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,68 @@
package com.iluwatar.layers;
import dto.CakeInfo;
import dto.CakeLayerInfo;
import dto.CakeToppingInfo;
import exception.CakeBakingException;
import lombok.extern.slf4j.Slf4j;
import service.CakeBakingService;
import view.CakeViewImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class Runner implements CommandLineRunner {
private final CakeBakingService cakeBakingService;
public static final String STRAWBERRY = "strawberry";
@Autowired
public Runner(CakeBakingService cakeBakingService) {
this.cakeBakingService = cakeBakingService;
}
@Override
public void run(String... args) {
//initialize sample data
initializeData();
// create view and render it
var cakeView = new CakeViewImpl(cakeBakingService);
cakeView.render();
}
/**
* Initializes the example data.
*/
private void initializeData() {
cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200));
cakeBakingService.saveNewLayer(new CakeLayerInfo("banana", 900));
cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950));
cakeBakingService.saveNewLayer(new CakeLayerInfo("lemon", 950));
cakeBakingService.saveNewLayer(new CakeLayerInfo("vanilla", 950));
cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950));
cakeBakingService.saveNewTopping(new CakeToppingInfo("candies", 350));
cakeBakingService.saveNewTopping(new CakeToppingInfo("cherry", 350));
var cake1 = new CakeInfo(new CakeToppingInfo("candies", 0), List.of(
new CakeLayerInfo("chocolate", 0),
new CakeLayerInfo("banana", 0),
new CakeLayerInfo(STRAWBERRY, 0)));
try {
cakeBakingService.bakeNewCake(cake1);
} catch (CakeBakingException e) {
LOGGER.error("Cake baking exception", e);
}
var cake2 = new CakeInfo(new CakeToppingInfo("cherry", 0), List.of(
new CakeLayerInfo("vanilla", 0),
new CakeLayerInfo("lemon", 0),
new CakeLayerInfo(STRAWBERRY, 0)));
try {
cakeBakingService.bakeNewCake(cake2);
} catch (CakeBakingException e) {
LOGGER.error("Cake baking exception", e);
}
}
}
@@ -1,135 +0,0 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.app;
import com.iluwatar.layers.dao.CakeDao;
import com.iluwatar.layers.dao.CakeLayerDao;
import com.iluwatar.layers.dao.CakeToppingDao;
import com.iluwatar.layers.dto.CakeInfo;
import com.iluwatar.layers.dto.CakeLayerInfo;
import com.iluwatar.layers.dto.CakeToppingInfo;
import com.iluwatar.layers.entity.Cake;
import com.iluwatar.layers.entity.CakeLayer;
import com.iluwatar.layers.entity.CakeTopping;
import com.iluwatar.layers.exception.CakeBakingException;
import com.iluwatar.layers.service.CakeBakingService;
import com.iluwatar.layers.service.CakeBakingServiceImpl;
import com.iluwatar.layers.view.CakeViewImpl;
import java.util.List;
/**
* Layers is an architectural style where software responsibilities are divided among the
* different layers of the application.
*
* <p>This example demonstrates a traditional 3-layer architecture consisting of data access
* layer, business layer and presentation layer.
*
* <p>The data access layer is formed of Spring Data repositories <code>CakeDao</code>,
* <code>CakeToppingDao</code> and <code>CakeLayerDao</code>. The repositories can be used
* for CRUD operations on cakes, cake toppings and cake layers respectively.
*
* <p>The business layer is built on top of the data access layer. <code>CakeBakingService</code>
* offers methods to retrieve available cake toppings and cake layers and baked cakes. Also the
* service is used to create new cakes out of cake toppings and cake layers.
*
* <p>The presentation layer is built on the business layer and in this example it simply lists
* the cakes that have been baked.
*
* <p>We have applied so called strict layering which means that the layers can only access the
* classes directly beneath them. This leads the solution to create an additional set of DTOs
* ( <code>CakeInfo</code>, <code>CakeToppingInfo</code>, <code>CakeLayerInfo</code>) to translate
* data between layers. In other words, <code>CakeBakingService</code> cannot return entities
* ( <code>Cake</code>, <code>CakeTopping</code>, <code>CakeLayer</code>) directly since these
* reside on data access layer but instead translates these into business layer DTOs
* (<code>CakeInfo</code>, <code>CakeToppingInfo</code>, <code>CakeLayerInfo</code>) and returns
* them instead. This way the presentation layer does not have any knowledge of other layers than
* the business layer and thus is not affected by changes to them.
*
* @see Cake
* @see CakeTopping
* @see CakeLayer
* @see CakeDao
* @see CakeToppingDao
* @see CakeLayerDao
* @see CakeBakingService
* @see CakeInfo
* @see CakeToppingInfo
* @see CakeLayerInfo
*
*/
public class App {
private static final CakeBakingService cakeBakingService = new CakeBakingServiceImpl();
public static final String STRAWBERRY = "strawberry";
/**
* Application entry point.
*
* @param args Command line parameters
*/
public static void main(String[] args) {
// initialize example data
initializeData(cakeBakingService);
// create view and render it
var cakeView = new CakeViewImpl(cakeBakingService);
cakeView.render();
}
/**
* Initializes the example data.
*/
private static void initializeData(CakeBakingService cakeBakingService) {
cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200));
cakeBakingService.saveNewLayer(new CakeLayerInfo("banana", 900));
cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950));
cakeBakingService.saveNewLayer(new CakeLayerInfo("lemon", 950));
cakeBakingService.saveNewLayer(new CakeLayerInfo("vanilla", 950));
cakeBakingService.saveNewLayer(new CakeLayerInfo(STRAWBERRY, 950));
cakeBakingService.saveNewTopping(new CakeToppingInfo("candies", 350));
cakeBakingService.saveNewTopping(new CakeToppingInfo("cherry", 350));
var cake1 = new CakeInfo(new CakeToppingInfo("candies", 0), List.of(
new CakeLayerInfo("chocolate", 0),
new CakeLayerInfo("banana", 0),
new CakeLayerInfo(STRAWBERRY, 0)));
try {
cakeBakingService.bakeNewCake(cake1);
} catch (CakeBakingException e) {
e.printStackTrace();
}
var cake2 = new CakeInfo(new CakeToppingInfo("cherry", 0), List.of(
new CakeLayerInfo("vanilla", 0),
new CakeLayerInfo("lemon", 0),
new CakeLayerInfo(STRAWBERRY, 0)));
try {
cakeBakingService.bakeNewCake(cake2);
} catch (CakeBakingException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,20 @@
package com.iluwatar.layers.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableJpaRepositories(basePackages = "dao")
@EntityScan(basePackages = "entity")
@ComponentScan(basePackages = {"com.iluwatar.layers", "service", "dto", "exception", "view" ,"dao"})
public class LayersApp {
public static void main(String[] args) {
SpringApplication.run(LayersApp.class, args);
}
}
@@ -1,94 +0,0 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.entity;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
/**
* CakeLayer entity.
*/
@Entity
public class CakeLayer {
@Id
@GeneratedValue
private Long id;
private String name;
private int calories;
@ManyToOne(cascade = CascadeType.ALL)
private Cake cake;
public CakeLayer() {
}
public CakeLayer(String name, int calories) {
this.setName(name);
this.setCalories(calories);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCalories() {
return calories;
}
public void setCalories(int calories) {
this.calories = calories;
}
@Override
public String toString() {
return String.format("id=%s name=%s calories=%d", id, name, calories);
}
public Cake getCake() {
return cake;
}
public void setCake(Cake cake) {
this.cake = cake;
}
}
@@ -1,94 +0,0 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.entity;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
/**
* CakeTopping entity.
*/
@Entity
public class CakeTopping {
@Id
@GeneratedValue
private Long id;
private String name;
private int calories;
@OneToOne(cascade = CascadeType.ALL)
private Cake cake;
public CakeTopping() {
}
public CakeTopping(String name, int calories) {
this.setName(name);
this.setCalories(calories);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCalories() {
return calories;
}
public void setCalories(int calories) {
this.calories = calories;
}
@Override
public String toString() {
return String.format("id=%s name=%s calories=%d", id, name, calories);
}
public Cake getCake() {
return cake;
}
public void setCake(Cake cake) {
this.cake = cake;
}
}
@@ -1,175 +0,0 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.service;
import com.iluwatar.layers.dao.CakeDao;
import com.iluwatar.layers.dao.CakeLayerDao;
import com.iluwatar.layers.dao.CakeToppingDao;
import com.iluwatar.layers.dto.CakeInfo;
import com.iluwatar.layers.dto.CakeLayerInfo;
import com.iluwatar.layers.dto.CakeToppingInfo;
import com.iluwatar.layers.entity.Cake;
import com.iluwatar.layers.entity.CakeLayer;
import com.iluwatar.layers.entity.CakeTopping;
import com.iluwatar.layers.exception.CakeBakingException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Implementation of CakeBakingService.
*/
@Service
@Transactional
public class CakeBakingServiceImpl implements CakeBakingService {
private final AbstractApplicationContext context;
public CakeBakingServiceImpl() {
this.context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Override
public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException {
var allToppings = getAvailableToppingEntities();
var matchingToppings =
allToppings.stream().filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name))
.toList();
if (matchingToppings.isEmpty()) {
throw new CakeBakingException(String.format("Topping %s is not available",
cakeInfo.cakeToppingInfo.name));
}
var allLayers = getAvailableLayerEntities();
Set<CakeLayer> foundLayers = new HashSet<>();
for (var info : cakeInfo.cakeLayerInfos) {
var found = allLayers.stream().filter(layer -> layer.getName().equals(info.name)).findFirst();
if (found.isEmpty()) {
throw new CakeBakingException(String.format("Layer %s is not available", info.name));
} else {
foundLayers.add(found.get());
}
}
var toppingBean = context.getBean(CakeToppingDao.class);
var topping = toppingBean.findById(matchingToppings.iterator().next().getId());
var cakeBean = context.getBean(CakeDao.class);
if (topping.isPresent()) {
var cake = new Cake();
cake.setTopping(topping.get());
cake.setLayers(foundLayers);
cakeBean.save(cake);
topping.get().setCake(cake);
toppingBean.save(topping.get());
var layerBean = context.getBean(CakeLayerDao.class);
for (var layer : foundLayers) {
layer.setCake(cake);
layerBean.save(layer);
}
} else {
throw new CakeBakingException(String.format("Topping %s is not available",
cakeInfo.cakeToppingInfo.name));
}
}
@Override
public void saveNewTopping(CakeToppingInfo toppingInfo) {
var bean = context.getBean(CakeToppingDao.class);
bean.save(new CakeTopping(toppingInfo.name, toppingInfo.calories));
}
@Override
public void saveNewLayer(CakeLayerInfo layerInfo) {
var bean = context.getBean(CakeLayerDao.class);
bean.save(new CakeLayer(layerInfo.name, layerInfo.calories));
}
private List<CakeTopping> getAvailableToppingEntities() {
var bean = context.getBean(CakeToppingDao.class);
List<CakeTopping> result = new ArrayList<>();
for (CakeTopping topping : bean.findAll()) {
if (topping.getCake() == null) {
result.add(topping);
}
}
return result;
}
@Override
public List<CakeToppingInfo> getAvailableToppings() {
var bean = context.getBean(CakeToppingDao.class);
List<CakeToppingInfo> result = new ArrayList<>();
for (CakeTopping next : bean.findAll()) {
if (next.getCake() == null) {
result.add(new CakeToppingInfo(next.getId(), next.getName(), next.getCalories()));
}
}
return result;
}
private List<CakeLayer> getAvailableLayerEntities() {
var bean = context.getBean(CakeLayerDao.class);
List<CakeLayer> result = new ArrayList<>();
for (CakeLayer next : bean.findAll()) {
if (next.getCake() == null) {
result.add(next);
}
}
return result;
}
@Override
public List<CakeLayerInfo> getAvailableLayers() {
var bean = context.getBean(CakeLayerDao.class);
List<CakeLayerInfo> result = new ArrayList<>();
for (CakeLayer next : bean.findAll()) {
if (next.getCake() == null) {
result.add(new CakeLayerInfo(next.getId(), next.getName(), next.getCalories()));
}
}
return result;
}
@Override
public List<CakeInfo> getAllCakes() {
var cakeBean = context.getBean(CakeDao.class);
List<CakeInfo> result = new ArrayList<>();
for (Cake cake : cakeBean.findAll()) {
var cakeToppingInfo =
new CakeToppingInfo(cake.getTopping().getId(), cake.getTopping().getName(), cake
.getTopping().getCalories());
List<CakeLayerInfo> cakeLayerInfos = new ArrayList<>();
for (var layer : cake.getLayers()) {
cakeLayerInfos.add(new CakeLayerInfo(layer.getId(), layer.getName(), layer.getCalories()));
}
var cakeInfo = new CakeInfo(cake.getId(), cakeToppingInfo, cakeLayerInfos);
result.add(cakeInfo);
}
return result;
}
}
@@ -22,16 +22,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.dao;
package dao;
import com.iluwatar.layers.entity.Cake;
import org.springframework.data.repository.CrudRepository;
import entity.Cake;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* CRUD repository for cakes.
*/
@Repository
public interface CakeDao extends CrudRepository<Cake, Long> {
}
public interface CakeDao extends JpaRepository<Cake, Long> {}
@@ -22,16 +22,16 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.dao;
package dao;
import com.iluwatar.layers.entity.CakeLayer;
import org.springframework.data.repository.CrudRepository;
import entity.CakeLayer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* CRUD repository for cake layers.
*/
@Repository
public interface CakeLayerDao extends CrudRepository<CakeLayer, Long> {
public interface CakeLayerDao extends JpaRepository<CakeLayer, Long> {
}
@@ -22,16 +22,18 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.dao;
package dao;
import com.iluwatar.layers.entity.CakeTopping;
import org.springframework.data.repository.CrudRepository;
import entity.CakeTopping;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* CRUD repository cake toppings.
*/
@Repository
public interface CakeToppingDao extends CrudRepository<CakeTopping, Long> {
public interface CakeToppingDao extends JpaRepository<CakeTopping, Long> {
}
@@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.dto;
package dto;
import java.util.List;
import java.util.Optional;
@@ -32,40 +32,40 @@ import java.util.Optional;
*/
public class CakeInfo {
public final Optional<Long> id;
public final CakeToppingInfo cakeToppingInfo;
public final List<CakeLayerInfo> cakeLayerInfos;
public final Optional<Long> id;
public final CakeToppingInfo cakeToppingInfo;
public final List<CakeLayerInfo> cakeLayerInfos;
/**
* Constructor.
*/
public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List<CakeLayerInfo> cakeLayerInfos) {
this.id = Optional.of(id);
this.cakeToppingInfo = cakeToppingInfo;
this.cakeLayerInfos = cakeLayerInfos;
}
/**
* Constructor.
*/
public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List<CakeLayerInfo> cakeLayerInfos) {
this.id = Optional.of(id);
this.cakeToppingInfo = cakeToppingInfo;
this.cakeLayerInfos = cakeLayerInfos;
}
/**
* Constructor.
*/
public CakeInfo(CakeToppingInfo cakeToppingInfo, List<CakeLayerInfo> cakeLayerInfos) {
this.id = Optional.empty();
this.cakeToppingInfo = cakeToppingInfo;
this.cakeLayerInfos = cakeLayerInfos;
}
/**
* Constructor.
*/
public CakeInfo(CakeToppingInfo cakeToppingInfo, List<CakeLayerInfo> cakeLayerInfos) {
this.id = Optional.empty();
this.cakeToppingInfo = cakeToppingInfo;
this.cakeLayerInfos = cakeLayerInfos;
}
/**
* Calculate calories.
*/
public int calculateTotalCalories() {
var total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0;
total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum();
return total;
}
/**
* Calculate calories.
*/
public int calculateTotalCalories() {
var total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0;
total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum();
return total;
}
@Override
public String toString() {
return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.orElse(-1L),
cakeToppingInfo, cakeLayerInfos, calculateTotalCalories());
}
@Override
public String toString() {
return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.orElse(-1L),
cakeToppingInfo, cakeLayerInfos, calculateTotalCalories());
}
}
@@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.dto;
package dto;
import java.util.Optional;
@@ -31,30 +31,30 @@ import java.util.Optional;
*/
public class CakeLayerInfo {
public final Optional<Long> id;
public final String name;
public final int calories;
public final Optional<Long> id;
public final String name;
public final int calories;
/**
* Constructor.
*/
public CakeLayerInfo(Long id, String name, int calories) {
this.id = Optional.of(id);
this.name = name;
this.calories = calories;
}
/**
* Constructor.
*/
public CakeLayerInfo(Long id, String name, int calories) {
this.id = Optional.of(id);
this.name = name;
this.calories = calories;
}
/**
* Constructor.
*/
public CakeLayerInfo(String name, int calories) {
this.id = Optional.empty();
this.name = name;
this.calories = calories;
}
/**
* Constructor.
*/
public CakeLayerInfo(String name, int calories) {
this.id = Optional.empty();
this.name = name;
this.calories = calories;
}
@Override
public String toString() {
return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories);
}
@Override
public String toString() {
return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories);
}
}
@@ -22,7 +22,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.dto;
package dto;
import java.util.Optional;
@@ -31,31 +32,31 @@ import java.util.Optional;
*/
public class CakeToppingInfo {
public final Optional<Long> id;
public final String name;
public final int calories;
public final Optional<Long> id;
public final String name;
public final int calories;
/**
* Constructor.
*/
public CakeToppingInfo(Long id, String name, int calories) {
this.id = Optional.of(id);
this.name = name;
this.calories = calories;
}
/**
* Constructor.
*/
public CakeToppingInfo(Long id, String name, int calories) {
this.id = Optional.of(id);
this.name = name;
this.calories = calories;
}
/**
* Constructor.
*/
public CakeToppingInfo(String name, int calories) {
this.id = Optional.empty();
this.name = name;
this.calories = calories;
}
/**
* Constructor.
*/
public CakeToppingInfo(String name, int calories) {
this.id = Optional.empty();
this.name = name;
this.calories = calories;
}
@Override
public String toString() {
return String.format("CakeToppingInfo id=%d name=%s calories=%d",
id.orElse(-1L), name, calories);
}
@Override
public String toString() {
return String.format("CakeToppingInfo id=%d name=%s calories=%d",
id.orElse(-1L), name, calories);
}
}
@@ -22,17 +22,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.entity;
package entity;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.CascadeType;
import jakarta.persistence.FetchType;
/**
* Cake entity.
@@ -40,50 +40,50 @@ import javax.persistence.OneToOne;
@Entity
public class Cake {
@Id
@GeneratedValue
private Long id;
@Id
@GeneratedValue
private Long id;
@OneToOne(cascade = CascadeType.REMOVE)
private CakeTopping topping;
@OneToOne(cascade = CascadeType.REMOVE)
private CakeTopping topping;
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<CakeLayer> layers;
@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<CakeLayer> layers;
public Cake() {
setLayers(new HashSet<>());
}
public Cake() {
setLayers(new HashSet<>());
}
public Long getId() {
return id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setId(Long id) {
this.id = id;
}
public CakeTopping getTopping() {
return topping;
}
public CakeTopping getTopping() {
return topping;
}
public void setTopping(CakeTopping topping) {
this.topping = topping;
}
public void setTopping(CakeTopping topping) {
this.topping = topping;
}
public Set<CakeLayer> getLayers() {
return layers;
}
public Set<CakeLayer> getLayers() {
return layers;
}
public void setLayers(Set<CakeLayer> layers) {
this.layers = layers;
}
public void setLayers(Set<CakeLayer> layers) {
this.layers = layers;
}
public void addLayer(CakeLayer layer) {
this.layers.add(layer);
}
public void addLayer(CakeLayer layer) {
this.layers.add(layer);
}
@Override
public String toString() {
return String.format("id=%s topping=%s layers=%s", id, topping, layers.toString());
}
@Override
public String toString() {
return String.format("id=%s topping=%s layers=%s", id, topping, layers.toString());
}
}
@@ -22,21 +22,42 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.app;
package entity;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import jakarta.persistence.*;
import lombok.*;
/**
*
* Application test
*
* CakeLayer entity.
*/
class AppTest {
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class CakeLayer {
@Test
void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
@Id
@GeneratedValue
private Long id;
private String name;
private int calories;
@ManyToOne(cascade = CascadeType.ALL)
private Cake cake;
public CakeLayer(String name, int calories) {
this.setName(name);
this.setCalories(calories);
}
@Override
public String toString() {
return String.format("id=%s name=%s calories=%d", id, name, calories);
}
}
@@ -0,0 +1,64 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package entity;
import jakarta.persistence.*;
import lombok.*;
/**
* CakeTopping entity.
*/
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class CakeTopping {
@Id
@GeneratedValue
private Long id;
private String name;
private int calories;
@OneToOne(cascade = CascadeType.ALL)
private Cake cake;
public CakeTopping(String name, int calories) {
this.setName(name);
this.setCalories(calories);
}
@Override
public String toString() {
return String.format("id=%s name=%s calories=%d", id, name, calories);
}
}
@@ -22,19 +22,22 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.exception;
package exception;
import org.springframework.stereotype.Component;
/**
* Custom exception used in cake baking.
*/
@Component
public class CakeBakingException extends Exception {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
public CakeBakingException() {
}
public CakeBakingException() {
}
public CakeBakingException(String message) {
super(message);
}
public CakeBakingException(String message) {
super(message);
}
}
@@ -22,46 +22,56 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.service;
package service;
import dto.CakeInfo;
import dto.CakeLayerInfo;
import dto.CakeToppingInfo;
import exception.CakeBakingException;
import org.springframework.stereotype.Service;
import com.iluwatar.layers.dto.CakeInfo;
import com.iluwatar.layers.dto.CakeLayerInfo;
import com.iluwatar.layers.dto.CakeToppingInfo;
import com.iluwatar.layers.exception.CakeBakingException;
import java.util.List;
/**
* Service for cake baking operations.
*/
@Service
public interface CakeBakingService {
/**
* Bakes new cake according to parameters.
*/
void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException;
/**
* Bakes new cake according to parameters.
*/
void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException;
/**
* Get all cakes.
*/
List<CakeInfo> getAllCakes();
/**
* Get all cakes.
*/
List<CakeInfo> getAllCakes();
/**
* Store new cake topping.
*/
void saveNewTopping(CakeToppingInfo toppingInfo);
/**
* Store new cake topping.
*/
void saveNewTopping(CakeToppingInfo toppingInfo);
/**
* Get available cake toppings.
*/
List<CakeToppingInfo> getAvailableToppings();
/**
* Get available cake toppings.
*/
List<CakeToppingInfo> getAvailableToppings();
/**
* Add new cake layer.
*/
void saveNewLayer(CakeLayerInfo layerInfo);
/**
* Add new cake layer.
*/
void saveNewLayer(CakeLayerInfo layerInfo);
/**
* Get available cake layers.
*/
List<CakeLayerInfo> getAvailableLayers();
void deleteAllCakes();
void deleteAllLayers();
void deleteAllToppings();
/**
* Get available cake layers.
*/
List<CakeLayerInfo> getAvailableLayers();
}
@@ -0,0 +1,189 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package service;
import dao.CakeDao;
import dao.CakeLayerDao;
import dao.CakeToppingDao;
import dto.CakeInfo;
import dto.CakeLayerInfo;
import dto.CakeToppingInfo;
import entity.Cake;
import entity.CakeLayer;
import entity.CakeTopping;
import exception.CakeBakingException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Implementation of CakeBakingService.
*/
@Service
@Transactional
public class CakeBakingServiceImpl implements CakeBakingService {
private final CakeDao cakeDao;
private final CakeLayerDao cakeLayerDao;
private final CakeToppingDao cakeToppingDao;
@Autowired
public CakeBakingServiceImpl(CakeDao cakeDao, CakeLayerDao cakeLayerDao, CakeToppingDao cakeToppingDao) {
this.cakeDao = cakeDao;
this.cakeLayerDao = cakeLayerDao;
this.cakeToppingDao = cakeToppingDao;
}
@Override
public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException {
var allToppings = getAvailableToppingEntities();
var matchingToppings =
allToppings.stream().filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name))
.toList();
if (matchingToppings.isEmpty()) {
throw new CakeBakingException(String.format("Topping %s is not available",
cakeInfo.cakeToppingInfo.name));
}
var allLayers = getAvailableLayerEntities();
Set<CakeLayer> foundLayers = new HashSet<>();
for (var info : cakeInfo.cakeLayerInfos) {
var found = allLayers.stream().filter(layer -> layer.getName().equals(info.name)).findFirst();
if (found.isEmpty()) {
throw new CakeBakingException(String.format("Layer %s is not available", info.name));
} else {
foundLayers.add(found.get());
}
}
var topping = cakeToppingDao.findById(matchingToppings.iterator().next().getId());
if (topping.isPresent()) {
var cake = new Cake();
cake.setTopping(topping.get());
cake.setLayers(foundLayers);
cakeDao.save(cake);
topping.get().setCake(cake);
cakeToppingDao.save(topping.get());
Set<CakeLayer> foundLayersToUpdate = new HashSet<>(foundLayers); // copy set to avoid a ConcurrentModificationException
for (var layer : foundLayersToUpdate) {
layer.setCake(cake);
cakeLayerDao.save(layer);
}
} else {
throw new CakeBakingException(String.format("Topping %s is not available",
cakeInfo.cakeToppingInfo.name));
}
}
@Override
public void saveNewTopping(CakeToppingInfo toppingInfo) {
cakeToppingDao.save(new CakeTopping(toppingInfo.name, toppingInfo.calories));
}
@Override
public void saveNewLayer(CakeLayerInfo layerInfo) {
cakeLayerDao.save(new CakeLayer(layerInfo.name, layerInfo.calories));
}
private List<CakeTopping> getAvailableToppingEntities() {
List<CakeTopping> result = new ArrayList<>();
for (CakeTopping topping : cakeToppingDao.findAll()) {
if (topping.getCake() == null) {
result.add(topping);
}
}
return result;
}
@Override
public List<CakeToppingInfo> getAvailableToppings() {
List<CakeToppingInfo> result = new ArrayList<>();
for (CakeTopping next : cakeToppingDao.findAll()) {
if (next.getCake() == null) {
result.add(new CakeToppingInfo(next.getId(), next.getName(), next.getCalories()));
}
}
return result;
}
private List<CakeLayer> getAvailableLayerEntities() {
List<CakeLayer> result = new ArrayList<>();
for (CakeLayer next : cakeLayerDao.findAll()) {
if (next.getCake() == null) {
result.add(next);
}
}
return result;
}
@Override
public List<CakeLayerInfo> getAvailableLayers() {
List<CakeLayerInfo> result = new ArrayList<>();
for (CakeLayer next : cakeLayerDao.findAll()) {
if (next.getCake() == null) {
result.add(new CakeLayerInfo(next.getId(), next.getName(), next.getCalories()));
}
}
return result;
}
@Override
public void deleteAllCakes() {
cakeDao.deleteAll();
}
@Override
public void deleteAllLayers() {
cakeLayerDao.deleteAll();
}
@Override
public void deleteAllToppings() {
cakeToppingDao.deleteAll();
}
@Override
public List<CakeInfo> getAllCakes() {
List<CakeInfo> result = new ArrayList<>();
for (Cake cake : cakeDao.findAll()) {
var cakeToppingInfo =
new CakeToppingInfo(cake.getTopping().getId(), cake.getTopping().getName(), cake
.getTopping().getCalories());
List<CakeLayerInfo> cakeLayerInfos = new ArrayList<>();
for (var layer : cake.getLayers()) {
cakeLayerInfos.add(new CakeLayerInfo(layer.getId(), layer.getName(), layer.getCalories()));
}
var cakeInfo = new CakeInfo(cake.getId(), cakeToppingInfo, cakeLayerInfos);
result.add(cakeInfo);
}
return result;
}
}
@@ -22,24 +22,26 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.view;
package view;
import com.iluwatar.layers.service.CakeBakingService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.CakeBakingService;
/**
* View implementation for displaying cakes.
*/
@Slf4j
public class CakeViewImpl implements View {
private final CakeBakingService cakeBakingService;
private final CakeBakingService cakeBakingService;
public CakeViewImpl(CakeBakingService cakeBakingService) {
this.cakeBakingService = cakeBakingService;
}
private static final Logger LOGGER = LoggerFactory.getLogger(CakeViewImpl.class);
public void render() {
cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString()));
}
public CakeViewImpl(CakeBakingService cakeBakingService) {
this.cakeBakingService = cakeBakingService;
}
public void render() {
cakeBakingService.getAllCakes().forEach(cake -> LOGGER.info(cake.toString()));
}
}
@@ -22,13 +22,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.layers.view;
package view;
/**
* View interface.
*/
public interface View {
void render();
void render();
}
@@ -24,30 +24,30 @@
-->
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>layers.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>layers-%d.log</fileNamePattern>
<maxHistory>5</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5p [%d{ISO8601,UTC}] %c: %m%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5p [%d{ISO8601,UTC}] %c: %m%n</pattern>
</encoder>
</appender>
<logger name="com.iluwatar" additivity="false">
<level value="DEBUG" />
<appender-ref ref="FILE" />
</logger>
<logger name="org.hibernate" additivity="false">
<level value="ERROR" />
<appender-ref ref="STDOUT" />
</logger>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>layers.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>layers-%d.log</fileNamePattern>
<maxHistory>5</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5p [%d{ISO8601,UTC}] %c: %m%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5p [%d{ISO8601,UTC}] %c: %m%n</pattern>
</encoder>
</appender>
<logger name="com.iluwatar" additivity="false">
<level value="DEBUG" />
<appender-ref ref="FILE" />
</logger>
<logger name="org.hibernate" additivity="false">
<level value="ERROR" />
<appender-ref ref="STDOUT" />
</logger>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright © 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="jpaData" />
</persistence>
@@ -0,0 +1,12 @@
spring.main.web-application-type=none
#datasource settings
spring.datasource.url=jdbc:h2:~/databases/cake
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
#data settings
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright © 2014-2021 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:security="http://www.springframework.org/schema/security" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<jpa:repositories base-package="com.iluwatar" />
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/databases/cake" />
<property name="username" value="sa" />
<property name="password" value="sa" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.iluwatar" />
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
</property>
<property name="jpaProperties">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<entry key="hibernate.hbm2ddl.auto" value="create-drop" />
<entry key="hibernate.show_sql" value="false" />
</map>
</property>
</bean>
<context:component-scan base-package="com.iluwatar.layers.dao">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>
</beans>
@@ -0,0 +1,24 @@
package com.iluwatar.layers.app;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest(classes = LayersApp.class)
class LayersAppTests {
private final ApplicationContext applicationContext;
@Autowired
LayersAppTests(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Test
void contextLoads() {
assertNotNull(applicationContext);
}
}
@@ -31,6 +31,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashSet;
import java.util.Set;
import entity.Cake;
import entity.CakeLayer;
import entity.CakeTopping;
import org.junit.jupiter.api.Test;
/**
@@ -27,6 +27,7 @@ package com.iluwatar.layers.exception;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import exception.CakeBakingException;
import org.junit.jupiter.api.Test;
/**
@@ -34,8 +35,10 @@ import org.junit.jupiter.api.Test;
*
* @author Jeroen Meulemeester
*/
class CakeBakingExceptionTest {
@Test
void testConstructor() {
final var exception = new CakeBakingException();
@@ -30,33 +30,56 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.iluwatar.layers.dto.CakeInfo;
import com.iluwatar.layers.dto.CakeLayerInfo;
import com.iluwatar.layers.dto.CakeToppingInfo;
import com.iluwatar.layers.exception.CakeBakingException;
import com.iluwatar.layers.app.LayersApp;
import dto.CakeInfo;
import dto.CakeLayerInfo;
import dto.CakeToppingInfo;
import exception.CakeBakingException;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import service.CakeBakingServiceImpl;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
/**
* Date: 12/15/15 - 9:55 PM
*
* @author Jeroen Meulemeester
*/
@SpringBootTest(classes = LayersApp.class)
class CakeBakingServiceImplTest {
@Test
void testLayers() {
final var service = new CakeBakingServiceImpl();
private final CakeBakingServiceImpl cakeBakingService;
final var initialLayers = service.getAvailableLayers();
@Autowired
CakeBakingServiceImplTest(CakeBakingServiceImpl cakeBakingService) {
this.cakeBakingService = cakeBakingService;
}
@BeforeEach
void setUp() {
cakeBakingService.deleteAllCakes();
cakeBakingService.deleteAllLayers();
cakeBakingService.deleteAllToppings();
}
@Test
void testLayers() {
final var initialLayers = cakeBakingService.getAvailableLayers();
assertNotNull(initialLayers);
assertTrue(initialLayers.isEmpty());
service.saveNewLayer(new CakeLayerInfo("Layer1", 1000));
service.saveNewLayer(new CakeLayerInfo("Layer2", 2000));
cakeBakingService.saveNewLayer(new CakeLayerInfo("Layer1", 1000));
cakeBakingService.saveNewLayer(new CakeLayerInfo("Layer2", 2000));
final var availableLayers = service.getAvailableLayers();
final var availableLayers = cakeBakingService.getAvailableLayers();
assertNotNull(availableLayers);
assertEquals(2, availableLayers.size());
for (final var layer : availableLayers) {
@@ -70,16 +93,14 @@ class CakeBakingServiceImplTest {
@Test
void testToppings() {
final var service = new CakeBakingServiceImpl();
final var initialToppings = service.getAvailableToppings();
final var initialToppings = cakeBakingService.getAvailableToppings();
assertNotNull(initialToppings);
assertTrue(initialToppings.isEmpty());
service.saveNewTopping(new CakeToppingInfo("Topping1", 1000));
service.saveNewTopping(new CakeToppingInfo("Topping2", 2000));
cakeBakingService.saveNewTopping(new CakeToppingInfo("Topping1", 1000));
cakeBakingService.saveNewTopping(new CakeToppingInfo("Topping2", 2000));
final var availableToppings = service.getAvailableToppings();
final var availableToppings = cakeBakingService.getAvailableToppings();
assertNotNull(availableToppings);
assertEquals(2, availableToppings.size());
for (final var topping : availableToppings) {
@@ -93,28 +114,27 @@ class CakeBakingServiceImplTest {
@Test
void testBakeCakes() throws CakeBakingException {
final var service = new CakeBakingServiceImpl();
final var initialCakes = service.getAllCakes();
final var initialCakes = cakeBakingService.getAllCakes();
assertNotNull(initialCakes);
assertTrue(initialCakes.isEmpty());
final var topping1 = new CakeToppingInfo("Topping1", 1000);
final var topping2 = new CakeToppingInfo("Topping2", 2000);
service.saveNewTopping(topping1);
service.saveNewTopping(topping2);
cakeBakingService.saveNewTopping(topping1);
cakeBakingService.saveNewTopping(topping2);
final var layer1 = new CakeLayerInfo("Layer1", 1000);
final var layer2 = new CakeLayerInfo("Layer2", 2000);
final var layer3 = new CakeLayerInfo("Layer3", 2000);
service.saveNewLayer(layer1);
service.saveNewLayer(layer2);
service.saveNewLayer(layer3);
cakeBakingService.saveNewLayer(layer1);
cakeBakingService.saveNewLayer(layer2);
cakeBakingService.saveNewLayer(layer3);
service.bakeNewCake(new CakeInfo(topping1, List.of(layer1, layer2)));
service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer3)));
cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, layer2)));
cakeBakingService.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer3)));
final var allCakes = service.getAllCakes();
final var allCakes = cakeBakingService.getAllCakes();
assertNotNull(allCakes);
assertEquals(2, allCakes.size());
for (final var cakeInfo : allCakes) {
@@ -130,61 +150,49 @@ class CakeBakingServiceImplTest {
@Test
void testBakeCakeMissingTopping() {
final var service = new CakeBakingServiceImpl();
final var layer1 = new CakeLayerInfo("Layer1", 1000);
final var layer2 = new CakeLayerInfo("Layer2", 2000);
service.saveNewLayer(layer1);
service.saveNewLayer(layer2);
cakeBakingService.saveNewLayer(layer1);
cakeBakingService.saveNewLayer(layer2);
final var missingTopping = new CakeToppingInfo("Topping1", 1000);
assertThrows(CakeBakingException.class, () -> {
service.bakeNewCake(new CakeInfo(missingTopping, List.of(layer1, layer2)));
});
assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(missingTopping, List.of(layer1, layer2))));
}
@Test
void testBakeCakeMissingLayer() {
final var service = new CakeBakingServiceImpl();
final var initialCakes = service.getAllCakes();
final var initialCakes = cakeBakingService.getAllCakes();
assertNotNull(initialCakes);
assertTrue(initialCakes.isEmpty());
final var topping1 = new CakeToppingInfo("Topping1", 1000);
service.saveNewTopping(topping1);
cakeBakingService.saveNewTopping(topping1);
final var layer1 = new CakeLayerInfo("Layer1", 1000);
service.saveNewLayer(layer1);
cakeBakingService.saveNewLayer(layer1);
final var missingLayer = new CakeLayerInfo("Layer2", 2000);
assertThrows(CakeBakingException.class, () -> {
service.bakeNewCake(new CakeInfo(topping1, List.of(layer1, missingLayer)));
});
assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, missingLayer))));
}
@Test
void testBakeCakesUsedLayer() throws CakeBakingException {
final var service = new CakeBakingServiceImpl();
final var initialCakes = service.getAllCakes();
final var initialCakes = cakeBakingService.getAllCakes();
assertNotNull(initialCakes);
assertTrue(initialCakes.isEmpty());
final var topping1 = new CakeToppingInfo("Topping1", 1000);
final var topping2 = new CakeToppingInfo("Topping2", 2000);
service.saveNewTopping(topping1);
service.saveNewTopping(topping2);
cakeBakingService.saveNewTopping(topping1);
cakeBakingService.saveNewTopping(topping2);
final var layer1 = new CakeLayerInfo("Layer1", 1000);
final var layer2 = new CakeLayerInfo("Layer2", 2000);
service.saveNewLayer(layer1);
service.saveNewLayer(layer2);
cakeBakingService.saveNewLayer(layer1);
cakeBakingService.saveNewLayer(layer2);
service.bakeNewCake(new CakeInfo(topping1, List.of(layer1, layer2)));
assertThrows(CakeBakingException.class, () -> {
service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2)));
});
cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, layer2)));
assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2))));
}
}
@@ -31,16 +31,17 @@ import static org.mockito.Mockito.when;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import com.iluwatar.layers.dto.CakeInfo;
import com.iluwatar.layers.dto.CakeLayerInfo;
import com.iluwatar.layers.dto.CakeToppingInfo;
import com.iluwatar.layers.service.CakeBakingService;
import dto.CakeInfo;
import dto.CakeLayerInfo;
import dto.CakeToppingInfo;
import service.CakeBakingService;
import java.util.LinkedList;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import view.CakeViewImpl;
/**
* Date: 12/15/15 - 10:04 PM
@@ -68,9 +69,9 @@ class CakeViewImplTest {
void testRender() {
final var layers = List.of(
new CakeLayerInfo("layer1", 1000),
new CakeLayerInfo("layer2", 2000),
new CakeLayerInfo("layer3", 3000));
new CakeLayerInfo("layer1", 1000),
new CakeLayerInfo("layer2", 2000),
new CakeLayerInfo("layer3", 3000));
final var cake = new CakeInfo(new CakeToppingInfo("topping", 1000), layers);
final var cakes = List.of(cake);
+1 -1
View File
@@ -333,7 +333,7 @@
<configuration>
<configLocation>google_checks.xml</configLocation>
<suppressionsLocation>checkstyle-suppressions.xml</suppressionsLocation>
<encoding>UTF-8</encoding>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
<includeTestSourceDirectory>false</includeTestSourceDirectory>