+ - getConnection() : Connection
+ - mutedClose(connection : Connection, statement : PreparedStatement, resultSet : ResultSet)
+ + update(room : Room) : boolean
+ }
+ class Hotel {
+ - LOGGER : Logger {static}
+ - hotelDao: HotelDaoImpl
+ + Hotel(hotelDao: HotelDaoImpl)
+ + bookRoom(roomNumber: Int)
+ + cancelRoomBooking(roomNumber: Int)
+ }
+}
+HotelDaoImpl ..|> HotelDao
+@enduml
diff --git a/transaction-script/pom.xml b/transaction-script/pom.xml
new file mode 100644
index 000000000..e53bb4b4a
--- /dev/null
+++ b/transaction-script/pom.xml
@@ -0,0 +1,73 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.23.0-SNAPSHOT
+
+ 4.0.0
+
+ transaction-script
+
+
+
+ com.h2database
+ h2
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.ashishtrivedi16.transactionscript.App
+
+
+
+
+
+
+
+
+
+
diff --git a/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/App.java b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/App.java
new file mode 100644
index 000000000..98762520d
--- /dev/null
+++ b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/App.java
@@ -0,0 +1,145 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import java.util.List;
+import javax.sql.DataSource;
+import org.h2.jdbcx.JdbcDataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Transaction Script (TS) is one of the simplest domain logic pattern.
+ * It needs less work to implement than other domain logic patterns and therefore
+ * it’s perfect fit for smaller applications that don't need big architecture behind them.
+ *
+ * In this example we will use the TS pattern to implement booking and cancellation
+ * methods for a Hotel management App. The main method will initialise an instance of
+ * {@link Hotel} and add rooms to it. After that it will book and cancel a couple of rooms
+ * and that will be printed by the logger.
+ *
+ * The thing we have to note here is that all the operations related to booking or cancelling
+ * a room like checking the database if the room exists, checking the booking status or the
+ * room, calculating refund price are all clubbed inside a single transaction script method.
+ */
+public class App {
+
+ private static final String H2_DB_URL = "jdbc:h2:~/test";
+ private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
+
+ /**
+ * Program entry point.
+ * Initialises an instance of Hotel and adds rooms to it.
+ * Carries out booking and cancel booking transactions.
+ * @param args command line arguments
+ * @throws Exception if any error occurs
+ */
+ public static void main(String[] args) throws Exception {
+
+ final var dataSource = createDataSource();
+ deleteSchema(dataSource);
+ createSchema(dataSource);
+ final var dao = new HotelDaoImpl(dataSource);
+
+ // Add rooms
+ addRooms(dao);
+
+ // Print room booking status
+ getRoomStatus(dao);
+
+ var hotel = new Hotel(dao);
+
+ // Book rooms
+ hotel.bookRoom(1);
+ hotel.bookRoom(2);
+ hotel.bookRoom(3);
+ hotel.bookRoom(4);
+ hotel.bookRoom(5);
+ hotel.bookRoom(6);
+
+ // Cancel booking for a few rooms
+ hotel.cancelRoomBooking(1);
+ hotel.cancelRoomBooking(3);
+ hotel.cancelRoomBooking(5);
+
+ getRoomStatus(dao);
+
+ deleteSchema(dataSource);
+
+ }
+
+ private static void getRoomStatus(HotelDaoImpl dao) throws Exception {
+ try (var customerStream = dao.getAll()) {
+ customerStream.forEach((customer) -> LOGGER.info(customer.toString()));
+ }
+ }
+
+ private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException {
+ try (var connection = dataSource.getConnection();
+ var statement = connection.createStatement()) {
+ statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
+ }
+ }
+
+ private static void createSchema(DataSource dataSource) throws Exception {
+ try (var connection = dataSource.getConnection();
+ var statement = connection.createStatement()) {
+ statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Get database.
+ *
+ * @return h2 datasource
+ */
+ private static DataSource createDataSource() {
+ var dataSource = new JdbcDataSource();
+ dataSource.setUrl(H2_DB_URL);
+ return dataSource;
+ }
+
+ private static void addRooms(HotelDaoImpl hotelDao) throws Exception {
+ for (var room : generateSampleRooms()) {
+ hotelDao.add(room);
+ }
+ }
+
+ /**
+ * Generate rooms.
+ *
+ * @return list of rooms
+ */
+ private static List generateSampleRooms() {
+ final var room1 = new Room(1, "Single", 50, false);
+ final var room2 = new Room(2, "Double", 80, false);
+ final var room3 = new Room(3, "Queen", 120, false);
+ final var room4 = new Room(4, "King", 150, false);
+ final var room5 = new Room(5, "Single", 50, false);
+ final var room6 = new Room(6, "Double", 80, false);
+ return List.of(room1, room2, room3, room4, room5, room6);
+ }
+}
diff --git a/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/Hotel.java b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/Hotel.java
new file mode 100644
index 000000000..be9000af5
--- /dev/null
+++ b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/Hotel.java
@@ -0,0 +1,87 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Hotel {
+ private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
+
+ private final HotelDaoImpl hotelDao;
+
+ public Hotel(HotelDaoImpl hotelDao) {
+ this.hotelDao = hotelDao;
+ }
+
+ /**
+ * Book a room.
+ *
+ * @param roomNumber room to book
+ * @throws Exception if any error
+ */
+ public void bookRoom(int roomNumber) throws Exception {
+
+ var room = hotelDao.getById(roomNumber);
+
+ if (room.isEmpty()) {
+ throw new Exception("Room number: " + roomNumber + " does not exist");
+ } else {
+ if (room.get().isBooked()) {
+ throw new Exception("Room already booked!");
+ } else {
+ var updateRoomBooking = room.get();
+ updateRoomBooking.setBooked(true);
+ hotelDao.update(updateRoomBooking);
+ }
+ }
+ }
+
+ /**
+ * Cancel a room booking.
+ *
+ * @param roomNumber room to cancel booking
+ * @throws Exception if any error
+ */
+ public void cancelRoomBooking(int roomNumber) throws Exception {
+
+ var room = hotelDao.getById(roomNumber);
+
+ if (room.isEmpty()) {
+ throw new Exception("Room number: " + roomNumber + " does not exist");
+ } else {
+ if (room.get().isBooked()) {
+ var updateRoomBooking = room.get();
+ updateRoomBooking.setBooked(false);
+ int refundAmount = updateRoomBooking.getPrice();
+ hotelDao.update(updateRoomBooking);
+
+ LOGGER.info("Booking cancelled for room number: " + roomNumber);
+ LOGGER.info(refundAmount + " is refunded");
+ } else {
+ throw new Exception("No booking for the room exists");
+ }
+ }
+ }
+}
diff --git a/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/HotelDao.java b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/HotelDao.java
new file mode 100644
index 000000000..71d9860ba
--- /dev/null
+++ b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/HotelDao.java
@@ -0,0 +1,40 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public interface HotelDao {
+
+ Stream getAll() throws Exception;
+
+ Optional getById(int id) throws Exception;
+
+ Boolean add(Room room) throws Exception;
+
+ Boolean update(Room room) throws Exception;
+
+ Boolean delete(Room room) throws Exception;
+}
diff --git a/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/HotelDaoImpl.java b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/HotelDaoImpl.java
new file mode 100644
index 000000000..e64b64699
--- /dev/null
+++ b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/HotelDaoImpl.java
@@ -0,0 +1,172 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.Optional;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import javax.sql.DataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HotelDaoImpl implements HotelDao {
+ private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
+
+ private final DataSource dataSource;
+
+ public HotelDaoImpl(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ @Override
+ public Stream getAll() throws Exception {
+ try {
+ var connection = getConnection();
+ var statement = connection.prepareStatement("SELECT * FROM ROOMS");
+ var resultSet = statement.executeQuery(); // NOSONAR
+ return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE,
+ Spliterator.ORDERED) {
+
+ @Override
+ public boolean tryAdvance(Consumer super Room> action) {
+ try {
+ if (!resultSet.next()) {
+ return false;
+ }
+ action.accept(createRoom(resultSet));
+ return true;
+ } catch (Exception e) {
+ throw new RuntimeException(e); // NOSONAR
+ }
+ }
+ }, false).onClose(() -> {
+ try {
+ mutedClose(connection, statement, resultSet);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage());
+ }
+ });
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public Optional getById(int id) throws Exception {
+ ResultSet resultSet = null;
+
+ try (var connection = getConnection();
+ var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) {
+
+ statement.setInt(1, id);
+ resultSet = statement.executeQuery();
+ if (resultSet.next()) {
+ return Optional.of(createRoom(resultSet));
+ } else {
+ return Optional.empty();
+ }
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ } finally {
+ if (resultSet != null) {
+ resultSet.close();
+ }
+ }
+ }
+
+ @Override
+ public Boolean add(Room room) throws Exception {
+ if (getById(room.getId()).isPresent()) {
+ return false;
+ }
+
+ try (var connection = getConnection();
+ var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) {
+ statement.setInt(1, room.getId());
+ statement.setString(2, room.getRoomType());
+ statement.setInt(3, room.getPrice());
+ statement.setBoolean(4, room.isBooked());
+ statement.execute();
+ return true;
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public Boolean update(Room room) throws Exception {
+ try (var connection = getConnection();
+ var statement =
+ connection
+ .prepareStatement("UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?"
+ + " WHERE ID = ?")) {
+ statement.setString(1, room.getRoomType());
+ statement.setInt(2, room.getPrice());
+ statement.setBoolean(3, room.isBooked());
+ statement.setInt(4, room.getId());
+ return statement.executeUpdate() > 0;
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public Boolean delete(Room room) throws Exception {
+ try (var connection = getConnection();
+ var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) {
+ statement.setInt(1, room.getId());
+ return statement.executeUpdate() > 0;
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ private Connection getConnection() throws Exception {
+ return dataSource.getConnection();
+ }
+
+ private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet)
+ throws Exception {
+ try {
+ resultSet.close();
+ statement.close();
+ connection.close();
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ private Room createRoom(ResultSet resultSet) throws Exception {
+ return new Room(resultSet.getInt("ID"),
+ resultSet.getString("ROOM_TYPE"),
+ resultSet.getInt("PRICE"),
+ resultSet.getBoolean("BOOKED"));
+ }
+}
diff --git a/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/Room.java b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/Room.java
new file mode 100644
index 000000000..c4ad7bfd6
--- /dev/null
+++ b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/Room.java
@@ -0,0 +1,123 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+/**
+ * A room POJO that represents the data that will be read from the data source.
+ */
+public class Room {
+
+ private int id;
+ private String roomType;
+ private int price;
+ private boolean booked;
+
+ /**
+ * Create an instance of room.
+ * @param id room id
+ * @param roomType room type
+ * @param price room price
+ * @param booked room booking status
+ */
+ public Room(int id, String roomType, int price, boolean booked) {
+ this.id = id;
+ this.roomType = roomType;
+ this.price = price;
+ this.booked = booked;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getRoomType() {
+ return roomType;
+ }
+
+ public void setRoomType(String roomType) {
+ this.roomType = roomType;
+ }
+
+ public int getPrice() {
+ return price;
+ }
+
+ public void setPrice(int price) {
+ this.price = price;
+ }
+
+ public boolean isBooked() {
+ return booked;
+ }
+
+ public void setBooked(boolean booked) {
+ this.booked = booked;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Room room = (Room) o;
+
+ if (id != room.id) {
+ return false;
+ }
+ if (price != room.price) {
+ return false;
+ }
+ if (booked != room.booked) {
+ return false;
+ }
+ return roomType.equals(room.roomType);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id;
+ result = 31 * result + roomType.hashCode();
+ result = 31 * result + price;
+ result = 31 * result + (booked ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Room{"
+ + "id=" + id
+ + ", roomType=" + roomType
+ + ", price=" + price
+ + ", booked=" + booked
+ + '}';
+ }
+}
diff --git a/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/RoomSchemaSql.java b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/RoomSchemaSql.java
new file mode 100644
index 000000000..bb2e2374b
--- /dev/null
+++ b/transaction-script/src/main/java/com/ashishtrivedi16/transactionscript/RoomSchemaSql.java
@@ -0,0 +1,38 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+/**
+ * Customer Schema SQL Class.
+ */
+public final class RoomSchemaSql {
+
+ public static final String CREATE_SCHEMA_SQL =
+ "CREATE TABLE ROOMS (ID NUMBER, ROOM_TYPE VARCHAR(100), PRICE INT(100), BOOKED VARCHAR(100))";
+ public static final String DELETE_SCHEMA_SQL = "DROP TABLE ROOMS IF EXISTS";
+
+ private RoomSchemaSql() {
+ }
+
+}
diff --git a/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/AppTest.java b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/AppTest.java
new file mode 100644
index 000000000..755a95653
--- /dev/null
+++ b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/AppTest.java
@@ -0,0 +1,36 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that Transaction script example runs without errors.
+ */
+public class AppTest {
+ @Test
+ public void test() throws Exception {
+ App.main(new String[]{});
+ }
+}
diff --git a/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/HotelDaoImplTest.java b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/HotelDaoImplTest.java
new file mode 100644
index 000000000..6e907fdc0
--- /dev/null
+++ b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/HotelDaoImplTest.java
@@ -0,0 +1,272 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import org.h2.jdbcx.JdbcDataSource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests {@link HotelDaoImpl}.
+ */
+public class HotelDaoImplTest {
+
+ private static final String DB_URL = "jdbc:h2:~/test";
+ private HotelDaoImpl dao;
+ private Room existingRoom = new Room(1, "Single", 50, false);
+
+ /**
+ * Creates rooms schema.
+ *
+ * @throws SQLException if there is any error while creating schema.
+ */
+ @BeforeEach
+ public void createSchema() throws SQLException {
+ try (var connection = DriverManager.getConnection(DB_URL);
+ var statement = connection.createStatement()) {
+ statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
+ statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
+ }
+ }
+
+ /**
+ * Represents the scenario where DB connectivity is present.
+ */
+ @Nested
+ public class ConnectionSuccess {
+
+ /**
+ * Setup for connection success scenario.
+ *
+ * @throws Exception if any error occurs.
+ */
+ @BeforeEach
+ public void setUp() throws Exception {
+ var dataSource = new JdbcDataSource();
+ dataSource.setURL(DB_URL);
+ dao = new HotelDaoImpl(dataSource);
+ var result = dao.add(existingRoom);
+ assertTrue(result);
+ }
+
+ /**
+ * Represents the scenario when DAO operations are being performed on a non existing room.
+ */
+ @Nested
+ public class NonExistingRoom {
+
+ @Test
+ public void addingShouldResultInSuccess() throws Exception {
+ try (var allRooms = dao.getAll()) {
+ assumeTrue(allRooms.count() == 1);
+ }
+
+ final var nonExistingRoom = new Room(2, "Double", 80, false);
+ var result = dao.add(nonExistingRoom);
+ assertTrue(result);
+
+ assertRoomCountIs(2);
+ assertEquals(nonExistingRoom, dao.getById(nonExistingRoom.getId()).get());
+ }
+
+ @Test
+ public void deletionShouldBeFailureAndNotAffectExistingRooms() throws Exception {
+ final var nonExistingRoom = new Room(2, "Double", 80, false);
+ var result = dao.delete(nonExistingRoom);
+
+ assertFalse(result);
+ assertRoomCountIs(1);
+ }
+
+ @Test
+ public void updationShouldBeFailureAndNotAffectExistingRooms() throws Exception {
+ final var nonExistingId = getNonExistingRoomId();
+ final var newRoomType = "Double";
+ final var newPrice = 80;
+ final var room = new Room(nonExistingId, newRoomType, newPrice, false);
+ var result = dao.update(room);
+
+ assertFalse(result);
+ assertFalse(dao.getById(nonExistingId).isPresent());
+ }
+
+ @Test
+ public void retrieveShouldReturnNoRoom() throws Exception {
+ assertFalse(dao.getById(getNonExistingRoomId()).isPresent());
+ }
+ }
+
+ /**
+ * Represents a scenario where DAO operations are being performed on an already existing
+ * room.
+ */
+ @Nested
+ public class ExistingRoom {
+
+ @Test
+ public void addingShouldResultInFailureAndNotAffectExistingRooms() throws Exception {
+ var existingRoom = new Room(1, "Single", 50, false);
+ var result = dao.add(existingRoom);
+
+ assertFalse(result);
+ assertRoomCountIs(1);
+ assertEquals(existingRoom, dao.getById(existingRoom.getId()).get());
+ }
+
+ @Test
+ public void deletionShouldBeSuccessAndRoomShouldBeNonAccessible() throws Exception {
+ var result = dao.delete(existingRoom);
+
+ assertTrue(result);
+ assertRoomCountIs(0);
+ assertFalse(dao.getById(existingRoom.getId()).isPresent());
+ }
+
+ @Test
+ public void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() throws
+ Exception {
+ final var newRoomType = "Double";
+ final var newPrice = 80;
+ final var newBookingStatus = false;
+ final var Room = new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus);
+ var result = dao.update(Room);
+
+ assertTrue(result);
+
+ final var room = dao.getById(existingRoom.getId()).get();
+ assertEquals(newRoomType, room.getRoomType());
+ assertEquals(newPrice, room.getPrice());
+ assertEquals(newBookingStatus, room.isBooked());
+ }
+ }
+ }
+
+ /**
+ * Represents a scenario where DB connectivity is not present due to network issue, or DB service
+ * unavailable.
+ */
+ @Nested
+ public class ConnectivityIssue {
+
+ private static final String EXCEPTION_CAUSE = "Connection not available";
+
+ /**
+ * setup a connection failure scenario.
+ *
+ * @throws SQLException if any error occurs.
+ */
+ @BeforeEach
+ public void setUp() throws SQLException {
+ dao = new HotelDaoImpl(mockedDatasource());
+ }
+
+ private DataSource mockedDatasource() throws SQLException {
+ var mockedDataSource = mock(DataSource.class);
+ var mockedConnection = mock(Connection.class);
+ var exception = new SQLException(EXCEPTION_CAUSE);
+ doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString());
+ doReturn(mockedConnection).when(mockedDataSource).getConnection();
+ return mockedDataSource;
+ }
+
+ @Test
+ public void addingARoomFailsWithExceptionAsFeedbackToClient() {
+ assertThrows(Exception.class, () -> {
+ dao.add(new Room(2, "Double", 80, false));
+ });
+ }
+
+ @Test
+ public void deletingARoomFailsWithExceptionAsFeedbackToTheClient() {
+ assertThrows(Exception.class, () -> {
+ dao.delete(existingRoom);
+ });
+ }
+
+ @Test
+ public void updatingARoomFailsWithFeedbackToTheClient() {
+ final var newRoomType = "Double";
+ final var newPrice = 80;
+ final var newBookingStatus = false;
+ assertThrows(Exception.class, () -> {
+ dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus));
+ });
+ }
+
+ @Test
+ public void retrievingARoomByIdFailsWithExceptionAsFeedbackToClient() {
+ assertThrows(Exception.class, () -> {
+ dao.getById(existingRoom.getId());
+ });
+ }
+
+ @Test
+ public void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() {
+ assertThrows(Exception.class, () -> {
+ dao.getAll();
+ });
+ }
+
+ }
+
+ /**
+ * Delete room schema for fresh setup per test.
+ *
+ * @throws SQLException if any error occurs.
+ */
+ @AfterEach
+ public void deleteSchema() throws SQLException {
+ try (var connection = DriverManager.getConnection(DB_URL);
+ var statement = connection.createStatement()) {
+ statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
+ }
+ }
+
+ private void assertRoomCountIs(int count) throws Exception {
+ try (var allRooms = dao.getAll()) {
+ assertEquals(count, allRooms.count());
+ }
+ }
+
+ /**
+ * An arbitrary number which does not correspond to an active Room id.
+ *
+ * @return an int of a room id which doesn't exist
+ */
+ private int getNonExistingRoomId() {
+ return 999;
+ }
+}
diff --git a/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/HotelTest.java b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/HotelTest.java
new file mode 100644
index 000000000..26bf4b5cd
--- /dev/null
+++ b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/HotelTest.java
@@ -0,0 +1,151 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import org.h2.jdbcx.JdbcDataSource;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Tests {@link Hotel}
+ */
+public class HotelTest {
+
+ private static final String H2_DB_URL = "jdbc:h2:~/test";
+
+ private Hotel hotel;
+ private HotelDaoImpl dao;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ final var dataSource = createDataSource();
+ deleteSchema(dataSource);
+ createSchema(dataSource);
+ dao = new HotelDaoImpl(dataSource);
+ addRooms(dao);
+ hotel = new Hotel(dao);
+
+ }
+
+ @Test
+ public void bookingRoomShouldChangeBookedStatusToTrue() throws Exception {
+ hotel.bookRoom(1);
+ assertTrue(dao.getById(1).get().isBooked());
+ }
+
+ @Test()
+ public void bookingRoomWithInvalidIdShouldRaiseException() {
+ assertThrows(Exception.class, () -> {
+ hotel.bookRoom(getNonExistingRoomId());
+ });
+ }
+
+ @Test()
+ public void bookingRoomAgainShouldRaiseException() {
+ assertThrows(Exception.class, () -> {
+ hotel.bookRoom(1);
+ hotel.bookRoom(1);
+ });
+ }
+
+ @Test
+ public void NotBookingRoomShouldNotChangeBookedStatus() throws Exception {
+ assertFalse(dao.getById(1).get().isBooked());
+ }
+
+ @Test
+ public void cancelRoomBookingShouldChangeBookedStatus() throws Exception {
+ hotel.bookRoom(1);
+ assertTrue(dao.getById(1).get().isBooked());
+ hotel.cancelRoomBooking(1);
+ assertFalse(dao.getById(1).get().isBooked());
+ }
+
+ @Test
+ public void cancelRoomBookingWithInvalidIdShouldRaiseException() {
+ assertThrows(Exception.class, () -> {
+ hotel.cancelRoomBooking(getNonExistingRoomId());
+ });
+ }
+
+ @Test
+ public void cancelRoomBookingForUnbookedRoomShouldRaiseException() {
+ assertThrows(Exception.class, () -> {
+ hotel.cancelRoomBooking(1);
+ });
+ }
+
+
+ private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException {
+ try (var connection = dataSource.getConnection();
+ var statement = connection.createStatement()) {
+ statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL);
+ }
+ }
+
+ private static void createSchema(DataSource dataSource) throws Exception {
+ try (var connection = dataSource.getConnection();
+ var statement = connection.createStatement()) {
+ statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL);
+ } catch (Exception e) {
+ throw new Exception(e.getMessage(), e);
+ }
+ }
+
+ public static DataSource createDataSource() {
+ JdbcDataSource dataSource = new JdbcDataSource();
+ dataSource.setUrl(H2_DB_URL);
+ return dataSource;
+ }
+
+ private static void addRooms(HotelDaoImpl hotelDao) throws Exception {
+ for (var room : generateSampleRooms()) {
+ hotelDao.add(room);
+ }
+ }
+
+ public static List generateSampleRooms() {
+ final var room1 = new Room(1, "Single", 50, false);
+ final var room2 = new Room(2, "Double", 80, false);
+ final var room3 = new Room(3, "Queen", 120, false);
+ final var room4 = new Room(4, "King", 150, false);
+ final var room5 = new Room(5, "Single", 50, false);
+ final var room6 = new Room(6, "Double", 80, false);
+ return List.of(room1, room2, room3, room4, room5, room6);
+ }
+
+ /**
+ * An arbitrary number which does not correspond to an active Room id.
+ *
+ * @return an int of a room id which doesn't exist
+ */
+ private int getNonExistingRoomId() {
+ return 999;
+ }
+}
diff --git a/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/RoomTest.java b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/RoomTest.java
new file mode 100644
index 000000000..9ea8756bd
--- /dev/null
+++ b/transaction-script/src/test/java/com/ashishtrivedi16/transactionscript/RoomTest.java
@@ -0,0 +1,95 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 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.ashishtrivedi16.transactionscript;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+/**
+ * Tests {@link Room}.
+ */
+public class RoomTest {
+
+ private Room room;
+ private static final int ID = 1;
+ private static final String ROOMTYPE = "Single";
+ private static final int PRICE = 50;
+ private static final boolean BOOKED = false;
+
+ @BeforeEach
+ public void setUp() {
+ room = new Room(ID, ROOMTYPE, PRICE, BOOKED);
+ }
+
+ @Test
+ public void getAndSetId() {
+ final var newId = 2;
+ room.setId(newId);
+ assertEquals(newId, room.getId());
+ }
+
+ @Test
+ public void getAndSetRoomType() {
+ final var newRoomType = "Double";
+ room.setRoomType(newRoomType);
+ assertEquals(newRoomType, room.getRoomType());
+ }
+
+ @Test
+ public void getAndSetLastName() {
+ final var newPrice = 60;
+ room.setPrice(newPrice);
+ assertEquals(newPrice, room.getPrice());
+ }
+
+ @Test
+ public void notEqualWithDifferentId() {
+ final var newId = 2;
+ final var otherRoom = new Room(newId, ROOMTYPE, PRICE, BOOKED);
+ assertNotEquals(room, otherRoom);
+ assertNotEquals(room.hashCode(), otherRoom.hashCode());
+ }
+
+ @Test
+ public void equalsWithSameObjectValues() {
+ final var otherRoom = new Room(ID, ROOMTYPE, PRICE, BOOKED);
+ assertEquals(room, otherRoom);
+ assertEquals(room.hashCode(), otherRoom.hashCode());
+ }
+
+ @Test
+ public void equalsWithSameObjects() {
+ assertEquals(room, room);
+ assertEquals(room.hashCode(), room.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals(String.format("Room{id=%s, roomType=%s, price=%s, booked=%s}",
+ room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), room.toString());
+ }
+}