Compare commits

..

8 Commits

Author SHA1 Message Date
d2fab302ce Refactor and add explanation for value object 2022-01-04 20:36:57 +02:00
2679f7aa6f docs: add castleKing1997 as a contributor for code (#1939)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-01-01 20:48:21 +02:00
8403fdacdd feature: Metadata Mapping pattern (#1932)
* metadata-mapping

* Update README.md

* add class diagram

* update README.md

* fix identation

* Update pom.xml

* fix indentation

* fix ci

* remove e.printstack

* fix ci

* update class diagram

* fix ci

* fix ci

* fix sc

* fix smells

* Update DatabaseUtil.java

* fix coverage

* Update DatabaseUtil.java

* Update DatabaseUtil.java

* Update DatabaseUtil.java

* Update metadata-mapping/README.md

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>

* fix review

* fix review

* Update App.java

* Update App.java

* fix review

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2022-01-01 20:46:20 +02:00
f670ae547b docs: add Leisterbecker as a contributor for code (#1935)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-12-23 20:45:14 +02:00
df73d80365 improvement: EventAggregator now considers Event-Type (#1933)
* Implemented more granular registration of observers with HashMap;
Key: Event, Value: List of Observers registered for the event;

* Edited constructors and super calls of all EventEmitter extenders

* Edited constructor calls in App.java

* Edited EventEmitterTest

* Added new white walkers event, scout emits at wednesday, varys observes and emits at saturday

* Added white walkers event to KingsHandTest.java

* Varys now passes events

* Corrected some indentation levels and added curly braces to if statements

* Corrected some styling

* Switched lines in App.java,
added javadoc to registerObserver

* Fixed some indents, added param comments
2021-12-23 20:43:36 +02:00
4588e09939 docs: add yuhangbin as a contributor for code (#1934)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-12-23 18:11:48 +02:00
69883196d2 improvement: Optimized NioReactor stop() (Reactor Pattern) (#1930)
* Optimized NioReactor stop()

* Optimized ThreadPoolDispatcher stop()
2021-12-23 18:10:17 +02:00
4dcc20b733 docs: add interactwithankush as a contributor for code (#1931)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-12-16 20:06:21 +05:30
30 changed files with 854 additions and 60 deletions

View File

@ -1758,6 +1758,33 @@
"contributions": [
"code"
]
},
{
"login": "yuhangbin",
"name": "CharlieYu",
"avatar_url": "https://avatars.githubusercontent.com/u/17566866?v=4",
"profile": "https://github.com/yuhangbin",
"contributions": [
"code"
]
},
{
"login": "Leisterbecker",
"name": "Leisterbecker",
"avatar_url": "https://avatars.githubusercontent.com/u/20650323?v=4",
"profile": "https://github.com/Leisterbecker",
"contributions": [
"code"
]
},
{
"login": "castleKing1997",
"name": "DragonDreamer",
"avatar_url": "https://avatars.githubusercontent.com/u/35420129?v=4",
"profile": "http://rosaecrucis.cn",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@ -10,7 +10,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-193-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-196-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
@ -323,6 +323,9 @@ This project is licensed under the terms of the MIT license.
<td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/Shrirang97"><img src="https://avatars.githubusercontent.com/u/28738668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shrirang</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AShrirang97" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Shrirang97" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
<td align="center"><a href="http://rosaecrucis.cn"><img src="https://avatars.githubusercontent.com/u/35420129?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DragonDreamer</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=castleKing1997" title="Code">💻</a></td>
</tr>
</table>

View File

@ -49,13 +49,28 @@ public class App {
public static void main(String[] args) {
var kingJoffrey = new KingJoffrey();
var kingsHand = new KingsHand(kingJoffrey);
var kingsHand = new KingsHand();
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
var varys = new LordVarys();
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
var scout = new Scout();
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
var emitters = List.of(
kingsHand,
new LordBaelish(kingsHand),
new LordVarys(kingsHand),
new Scout(kingsHand)
baelish,
varys,
scout
);
Arrays.stream(Weekday.values())

View File

@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum Event {
WHITE_WALKERS_SIGHTED("White walkers sighted"),
STARK_SIGHTED("Stark sighted"),
WARSHIPS_APPROACHING("Warships approaching"),
TRAITOR_DETECTED("Traitor detected");

View File

@ -23,31 +23,48 @@
package com.iluwatar.event.aggregator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* EventEmitter is the base class for event producers that can be observed.
*/
public abstract class EventEmitter {
private final List<EventObserver> observers;
private final Map<Event, List<EventObserver>> observerLists;
public EventEmitter() {
observers = new LinkedList<>();
observerLists = new HashMap<>();
}
public EventEmitter(EventObserver obs) {
public EventEmitter(EventObserver obs, Event e) {
this();
registerObserver(obs);
registerObserver(obs, e);
}
public final void registerObserver(EventObserver obs) {
observers.add(obs);
/**
* Registers observer for specific event in the related list.
*
* @param obs the observer that observers this emitter
* @param e the specific event for that observation occurs
* */
public final void registerObserver(EventObserver obs, Event e) {
if (!observerLists.containsKey(e)) {
observerLists.put(e, new LinkedList<>());
}
if (!observerLists.get(e).contains(obs)) {
observerLists.get(e).add(obs);
}
}
protected void notifyObservers(Event e) {
observers.forEach(obs -> obs.onEvent(e));
if (observerLists.containsKey(e)) {
observerLists
.get(e)
.forEach(observer -> observer.onEvent(e));
}
}
public abstract void timePasses(Weekday day);

View File

@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() {
}
public KingsHand(EventObserver obs) {
super(obs);
public KingsHand(EventObserver obs, Event e) {
super(obs, e);
}
@Override
@ -42,6 +42,5 @@ public class KingsHand extends EventEmitter implements EventObserver {
@Override
public void timePasses(Weekday day) {
// NOP
}
}

View File

@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
public LordBaelish() {
}
public LordBaelish(EventObserver obs) {
super(obs);
public LordBaelish(EventObserver obs, Event e) {
super(obs, e);
}
@Override

View File

@ -23,16 +23,19 @@
package com.iluwatar.event.aggregator;
import lombok.extern.slf4j.Slf4j;
/**
* LordVarys produces events.
*/
public class LordVarys extends EventEmitter {
@Slf4j
public class LordVarys extends EventEmitter implements EventObserver {
public LordVarys() {
}
public LordVarys(EventObserver obs) {
super(obs);
public LordVarys(EventObserver obs, Event e) {
super(obs, e);
}
@Override
@ -41,4 +44,10 @@ public class LordVarys extends EventEmitter {
notifyObservers(Event.TRAITOR_DETECTED);
}
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
}

View File

@ -31,8 +31,8 @@ public class Scout extends EventEmitter {
public Scout() {
}
public Scout(EventObserver obs) {
super(obs);
public Scout(EventObserver obs, Event e) {
super(obs, e);
}
@Override
@ -40,5 +40,8 @@ public class Scout extends EventEmitter {
if (day == Weekday.TUESDAY) {
notifyObservers(Event.WARSHIPS_APPROACHING);
}
if (day == Weekday.WEDNESDAY) {
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
}
}
}

View File

@ -31,6 +31,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@ -46,7 +47,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
/**
* Factory used to create a new instance of the test object with a default observer
*/
private final Function<EventObserver, E> factoryWithDefaultObserver;
private final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver;
/**
* Factory used to create a new instance of the test object without passing a default observer
@ -67,7 +68,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
* Create a new event emitter test, using the given test object factories, special day and event
*/
EventEmitterTest(final Weekday specialDay, final Event event,
final Function<EventObserver, E> factoryWithDefaultObserver,
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver,
final Supplier<E> factoryWithoutDefaultObserver) {
this.specialDay = specialDay;
@ -129,8 +130,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithoutDefaultObserver.get();
emitter.registerObserver(observer1);
emitter.registerObserver(observer2);
emitter.registerObserver(observer1, event);
emitter.registerObserver(observer2, event);
testAllDays(specialDay, event, emitter, observer1, observer2);
}
@ -146,9 +147,9 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer1 = mock(EventObserver.class);
final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver);
emitter.registerObserver(observer1);
emitter.registerObserver(observer2);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event);
emitter.registerObserver(observer1, event);
emitter.registerObserver(observer2, event);
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
}

View File

@ -55,7 +55,11 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
@Test
void testPassThrough() throws Exception {
final var observer = mock(EventObserver.class);
final var kingsHand = new KingsHand(observer);
final var kingsHand = new KingsHand();
kingsHand.registerObserver(observer, Event.STARK_SIGHTED);
kingsHand.registerObserver(observer, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(observer, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(observer, Event.WHITE_WALKERS_SIGHTED);
// The kings hand should not pass any events before he received one
verifyZeroInteractions(observer);

View File

@ -34,7 +34,9 @@ class ScoutTest extends EventEmitterTest<Scout> {
* Create a new test instance, using the correct object factory
*/
public ScoutTest() {
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
}
}

182
metadata-mapping/README.md Normal file
View File

@ -0,0 +1,182 @@
---
layout: pattern
title: Metadata Mapping
folder: metadata-mapping
permalink: /patterns/metadata-mapping/
categories: Architectural
language: en
tags:
- Data access
---
## Intent
Holds details of object-relational mapping in the metadata.
## Explanation
Real world example
> Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code.
In plain words
> Metadata Mapping specifies the mapping between classes and tables so that we could treat a table of any database like a Java class.
Wikipedia says
> Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language.
**Programmatic Example**
We give an example about visiting the information of `USER` table in `h2` database. Firstly, we create `USER` table with `h2`:
```java
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
+ "CREATE TABLE `user` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
/**
* Create database.
*/
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}
```
Correspondingly, here's the basic `User` entity.
```java
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
/**
* Get a user.
* @param username user name
* @param password user password
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
```
Then we write a `xml` file to show the mapping between the table and the object:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>
```
We use `Hibernate` to resolve the mapping and connect to our database, here's its configuration:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
```
Then we can get access to the table just like an object with `Hibernate`, here's some CRUDs:
```java
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
/**
* List all users.
* @return list of users
*/
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
// other CRUDs ->
...
public void close() {
HibernateUtil.shutdown();
}
}
```
## Class diagram
![metamapping](etc/metamapping.png)
## Applicability
Use the Metadata Mapping when:
- you want reduce the amount of work needed to handle database mapping.
## Known uses
[Hibernate](https://hibernate.org/), [EclipseLink](https://www.eclipse.org/eclipselink/), [MyBatis](https://blog.mybatis.org/)......
## Credits
- [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,32 @@
@startuml
interface com.iluwatar.metamapping.service.UserService {
+ List<User> listUser()
+ int createUser(User)
+ void updateUser(Integer,User)
+ void deleteUser(Integer)
+ User getUser(Integer)
+ void close()
}
class com.iluwatar.metamapping.utils.DatabaseUtil {
+ {static} void createDataSource()
}
class com.iluwatar.metamapping.model.User {
- Integer id
- String username
- String password
+ User(String username, String password)
}
class com.iluwatar.metamapping.utils.HibernateUtil {
+ {static} SessionFactory getSessionFactory()
+ {static} void shutdown()
}
class com.iluwatar.metamapping.App {
+ {static} void main(String[])
+ {static} List<User> generateSampleUsers()
}
com.iluwatar.metamapping.service.UserService <.. com.iluwatar.metamapping.App
com.iluwatar.metamapping.model.User <.. com.iluwatar.metamapping.service.UserService
com.iluwatar.metamapping.utils.HibernateUtil <.. com.iluwatar.metamapping.service.UserService
com.iluwatar.metamapping.utils.DatabaseUtil <-- com.iluwatar.metamapping.utils.HibernateUtil
@enduml

87
metadata-mapping/pom.xml Normal file
View File

@ -0,0 +1,87 @@
<?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.
-->
<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">
<parent>
<artifactId>java-design-patterns</artifactId>
<groupId>com.iluwatar</groupId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>metadata-mapping</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.metamapping.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,72 @@
package com.iluwatar.metamapping;
import com.iluwatar.metamapping.model.User;
import com.iluwatar.metamapping.service.UserService;
import com.iluwatar.metamapping.utils.DatabaseUtil;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.ServiceRegistry;
/**
* Metadata Mapping specifies the mapping
* between classes and tables so that
* we could treat a table of any database like a Java class.
*
* <p>With hibernate, we achieve list/create/update/delete/get operations:
* 1)Create the H2 Database in {@link DatabaseUtil}.
* 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete.
* For learning metadata mapping pattern, we go deeper into Hibernate here:
* a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml
* b)create session factory to generate session interacting with database
* c)generate session with factory pattern
* d)create query object or use basic api with session,
* hibernate will convert all query to database query according to metadata
* 3)We encapsulate hibernate service in {@link UserService} for our use.
* @see org.hibernate.cfg.Configuration#configure(String)
* @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry)
* @see org.hibernate.internal.SessionFactoryImpl#openSession()
*/
@Slf4j
public class App {
/**
* Program entry point.
*
* @param args command line args.
* @throws Exception if any error occurs.
*/
public static void main(String[] args) throws Exception {
// get service
var userService = new UserService();
// use create service to add users
for (var user: generateSampleUsers()) {
var id = userService.createUser(user);
LOGGER.info("Add user" + user + "at" + id + ".");
}
// use list service to get users
var users = userService.listUser();
LOGGER.info(String.valueOf(users));
// use get service to get a user
var user = userService.getUser(1);
LOGGER.info(String.valueOf(user));
// change password of user 1
user.setPassword("new123");
// use update service to update user 1
userService.updateUser(1, user);
// use delete service to delete user 2
userService.deleteUser(2);
// close service
userService.close();
}
/**
* Generate users.
*
* @return list of users.
*/
public static List<User> generateSampleUsers() {
final var user1 = new User("ZhangSan", "zhs123");
final var user2 = new User("LiSi", "ls123");
final var user3 = new User("WangWu", "ww123");
return List.of(user1, user2, user3);
}
}

View File

@ -0,0 +1,29 @@
package com.iluwatar.metamapping.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* User Entity.
*/
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
public User() {}
/**
* Get a user.
* @param username user name
* @param password user password
*/
public User(String username, String password) {
this.username = username;
this.password = password;
}
}

View File

@ -0,0 +1,114 @@
package com.iluwatar.metamapping.service;
import com.iluwatar.metamapping.model.User;
import com.iluwatar.metamapping.utils.HibernateUtil;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
/**
* Service layer for user.
*/
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
/**
* List all users.
* @return list of users
*/
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
/**
* Add a user.
* @param user user entity
* @return user id
*/
public int createUser(User user) {
LOGGER.info("create user: " + user.getUsername());
var id = -1;
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
id = (Integer) session.save(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to create user", e);
}
LOGGER.info("create user " + user.getUsername() + " at " + id);
return id;
}
/**
* Update user.
* @param id user id
* @param user new user entity
*/
public void updateUser(Integer id, User user) {
LOGGER.info("update user at " + id);
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
user.setId(id);
session.update(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to update user", e);
}
}
/**
* Delete user.
* @param id user id
*/
public void deleteUser(Integer id) {
LOGGER.info("delete user at: " + id);
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
var user = session.get(User.class, id);
session.delete(user);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to delete user", e);
}
}
/**
* Get user.
* @param id user id
* @return deleted user
*/
public User getUser(Integer id) {
LOGGER.info("get user at: " + id);
User user = null;
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
user = session.get(User.class, id);
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get user", e);
}
return user;
}
/**
* Close hibernate.
*/
public void close() {
HibernateUtil.shutdown();
}
}

View File

@ -0,0 +1,39 @@
package com.iluwatar.metamapping.utils;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;
import org.h2.jdbcx.JdbcDataSource;
/**
* Create h2 database.
*/
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user`;"
+ "CREATE TABLE `user` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
/**
* Hide constructor.
*/
private DatabaseUtil() {}
/**
* Create database.
*/
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}

View File

@ -0,0 +1,45 @@
package com.iluwatar.metamapping.utils;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Manage hibernate.
*/
@Slf4j
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
/**
* Hide constructor.
*/
private HibernateUtil() {}
/**
* Build session factory.
* @return session factory
*/
private static SessionFactory buildSessionFactory() {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
/**
* Get session factory.
* @return session factory
*/
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* Close session factory.
*/
public static void shutdown() {
// Close caches and connection pools
getSessionFactory().close();
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>

View File

@ -0,0 +1,20 @@
package com.iluwatar.metamapping;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that metadata mapping example runs without errors.
*/
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test
void shouldExecuteMetaMappingWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}

View File

@ -75,6 +75,7 @@
<license-maven-plugin.version>3.0</license-maven-plugin.version>
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
<commons-io.version>2.7</commons-io.version>
<istack-commons-runtime.version>4.0.1</istack-commons-runtime.version>
<!-- SonarCloud -->
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>iluwatar</sonar.organization>
@ -227,6 +228,7 @@
<module>lockable-object</module>
<module>fanout-fanin</module>
<module>domain-model</module>
<module>metadata-mapping</module>
</modules>
<repositories>
<repository>
@ -377,6 +379,11 @@
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>com.sun.istack</groupId>
<artifactId>istack-commons-runtime</artifactId>
<version>${istack-commons-runtime.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

View File

@ -96,9 +96,11 @@ public class NioReactor {
* @throws IOException if any I/O error occurs.
*/
public void stop() throws InterruptedException, IOException {
reactorMain.shutdownNow();
reactorMain.shutdown();
selector.wakeup();
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) {
reactorMain.shutdownNow();
}
selector.close();
LOGGER.info("Reactor stopped");
}

View File

@ -64,6 +64,8 @@ public class ThreadPoolDispatcher implements Dispatcher {
@Override
public void stop() throws InterruptedException {
executorService.shutdown();
executorService.awaitTermination(4, TimeUnit.SECONDS);
if (executorService.awaitTermination(4, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
}
}

View File

@ -10,19 +10,80 @@ tags:
---
## Intent
Provide objects which follow value semantics rather than reference semantics.
This means value objects' equality are not based on identity. Two value objects are
This means value objects' equality is not based on identity. Two value objects are
equal when they have the same value, not necessarily being the same object.
## Explanation
Real-world example
> There is a class for hero statistics in a role-playing game. The statistics contain attributes
> such as strength, intelligence, and luck. The statistics of different heroes should be equal
> when all the attributes are equal.
In plain words
> Value objects are equal when their attributes have the same value
Wikipedia says
> In computer science, a value object is a small object that represents a simple entity whose
> equality is not based on identity: i.e. two value objects are equal when they have the same
> value, not necessarily being the same object.
**Programmatic Example**
Here is the `HeroStat` class that is the value object. Notice the use of
[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation.
```java
@Value(staticConstructor = "valueOf")
class HeroStat {
int strength;
int intelligence;
int luck;
}
```
The example creates three different `HeroStat`s and compares their equality.
```java
var statA = HeroStat.valueOf(10, 5, 0);
var statB = HeroStat.valueOf(10, 5, 0);
var statC = HeroStat.valueOf(5, 1, 8);
LOGGER.info(statA.toString());
LOGGER.info(statB.toString());
LOGGER.info(statC.toString());
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
```
Here's the console output.
```
20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true
20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false
```
## Class diagram
![alt text](./etc/value-object.png "Value Object")
## Applicability
Use the Value Object when
* You need to measure the objects' equality based on the objects' value
* The object's equality needs to be based on the object's value
## Real world examples
## Known uses
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
@ -31,6 +92,7 @@ Use the Value Object when
## Credits
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
* [ValueObject](https://martinfowler.com/bliki/ValueObject.html)
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)

View File

@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
public class App {
/**
* This practice creates three HeroStats(Value object) and checks equality between those.
* This example creates three HeroStats (value objects) and checks equality between those.
*/
public static void main(String[] args) {
var statA = HeroStat.valueOf(10, 5, 0);
@ -51,6 +51,8 @@ public class App {
var statC = HeroStat.valueOf(5, 1, 8);
LOGGER.info(statA.toString());
LOGGER.info(statB.toString());
LOGGER.info(statC.toString());
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));

View File

@ -23,10 +23,7 @@
package com.iluwatar.value.object;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.Value;
/**
* HeroStat is a value object.
@ -35,23 +32,10 @@ import lombok.ToString;
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
* </a>
*/
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public class HeroStat {
// Stats for a hero
private final int strength;
private final int intelligence;
private final int luck;
// Static factory method to create new instances.
public static HeroStat valueOf(int strength, int intelligence, int luck) {
return new HeroStat(strength, intelligence, luck);
}
// The clone() method should not be public. Just don't override it.
@Value(staticConstructor = "valueOf")
class HeroStat {
int strength;
int intelligence;
int luck;
}