Merge branch 'unit-of-work-pattern' of github.com:piyushchaudhari04/java-design-patterns into unit-of-work-pattern

This commit is contained in:
Piyush Chaudhari 2017-09-26 00:07:03 +05:30
commit 04891e5bf3
23 changed files with 802 additions and 287 deletions

View File

@ -16,7 +16,131 @@ Avoid coupling the sender of a request to its receiver by giving
more than one object a chance to handle the request. Chain the receiving
objects and pass the request along the chain until an object handles it.
![alt text](./etc/chain_1.png "Chain of Responsibility")
## Explanation
Real world example
> The Orc King gives loud orders to his army. The closest one to react is the commander, then officer and then soldier. The commander, officer and soldier here form a chain of responsibility.
In plain words
> It helps building a chain of objects. Request enters from one end and keeps going from object to object till it finds the suitable handler.
Wikipedia says
> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.
**Programmatic Example**
Translating our example with orcs from above. First we have the request class
```
public class Request {
private final RequestType requestType;
private final String requestDescription;
private boolean handled;
public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}
public String getRequestDescription() { return requestDescription; }
public RequestType getRequestType() { return requestType; }
public void markHandled() { this.handled = true; }
public boolean isHandled() { return this.handled; }
@Override
public String toString() { return getRequestDescription(); }
}
public enum RequestType {
DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
}
```
Then the request handler hierarchy
```
public abstract class RequestHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
private RequestHandler next;
public RequestHandler(RequestHandler next) {
this.next = next;
}
public void handleRequest(Request req) {
if (next != null) {
next.handleRequest(req);
}
}
protected void printHandling(Request req) {
LOGGER.info("{} handling request \"{}\"", this, req);
}
@Override
public abstract String toString();
}
public class OrcCommander extends RequestHandler {
public OrcCommander(RequestHandler handler) {
super(handler);
}
@Override
public void handleRequest(Request req) {
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
printHandling(req);
req.markHandled();
} else {
super.handleRequest(req);
}
}
@Override
public String toString() {
return "Orc commander";
}
}
// OrcOfficer and OrcSoldier are defined similarly as OrcCommander
```
Then we have the Orc King who gives the orders and forms the chain
```
public class OrcKing {
RequestHandler chain;
public OrcKing() {
buildChain();
}
private void buildChain() {
chain = new OrcCommander(new OrcOfficer(new OrcSoldier(null)));
}
public void makeRequest(Request req) {
chain.handleRequest(req);
}
}
```
Then it is used as follows
```
OrcKing king = new OrcKing();
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); // Orc commander handling request "defend castle"
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); // Orc officer handling request "torture prisoner"
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); // Orc soldier handling request "collect tax"
```
## Applicability
Use Chain of Responsibility when

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,109 +0,0 @@
<?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 id="1" language="java" name="com.iluwatar.chain.Request" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/Request.java" binary="false" corner="BOTTOM_RIGHT">
<position height="196" width="228" x="168" y="182"/>
<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="2" language="java" name="com.iluwatar.chain.OrcOfficer" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="194" x="168" y="609"/>
<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.chain.OrcCommander" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/OrcCommander.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="217" x="402" y="609"/>
<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.chain.RequestHandler" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/RequestHandler.java" binary="false" corner="BOTTOM_RIGHT">
<position height="141" width="218" x="451" y="418"/>
<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.chain.OrcSoldier" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="194" x="659" y="609"/>
<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="6" language="java" name="com.iluwatar.chain.OrcKing" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/OrcKing.java" binary="false" corner="BOTTOM_RIGHT">
<position height="124" width="188" x="451" y="182"/>
<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>
<enumeration id="7" language="java" name="com.iluwatar.chain.RequestType" project="chain"
file="/chain/src/main/java/com/iluwatar/chain/RequestType.java" binary="false" corner="BOTTOM_RIGHT">
<position height="142" width="243" x="168" y="418"/>
<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>
</enumeration>
<association id="8">
<end type="SOURCE" refId="6" navigable="false">
<attribute id="9" name="chain"/>
<multiplicity id="10" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="11">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="12" name="next"/>
<multiplicity id="13" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<generalization id="14">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</generalization>
<association id="15">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="16" name="requestType"/>
<multiplicity id="17" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="7" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<generalization id="18">
<end type="SOURCE" refId="2"/>
<end type="TARGET" refId="4"/>
</generalization>
<generalization id="19">
<end type="SOURCE" refId="3"/>
<end type="TARGET" refId="4"/>
</generalization>
<classifier-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"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

View File

@ -1,61 +0,0 @@
@startuml
package com.iluwatar.chain {
class App {
+ App()
+ main(args : String[]) {static}
}
class OrcCommander {
+ OrcCommander(handler : RequestHandler)
+ handleRequest(req : Request)
+ toString() : String
}
class OrcKing {
~ chain : RequestHandler
+ OrcKing()
- buildChain()
+ makeRequest(req : Request)
}
class OrcOfficer {
+ OrcOfficer(handler : RequestHandler)
+ handleRequest(req : Request)
+ toString() : String
}
class OrcSoldier {
+ OrcSoldier(handler : RequestHandler)
+ handleRequest(req : Request)
+ toString() : String
}
class Request {
- handled : boolean
- requestDescription : String
- requestType : RequestType
+ Request(requestType : RequestType, requestDescription : String)
+ getRequestDescription() : String
+ getRequestType() : RequestType
+ isHandled() : boolean
+ markHandled()
+ toString() : String
}
abstract class RequestHandler {
- LOGGER : Logger {static}
- next : RequestHandler
+ RequestHandler(next : RequestHandler)
+ handleRequest(req : Request)
# printHandling(req : Request)
+ toString() : String {abstract}
}
enum RequestType {
+ COLLECT_TAX {static}
+ DEFEND_CASTLE {static}
+ TORTURE_PRISONER {static}
+ valueOf(name : String) : RequestType {static}
+ values() : RequestType[] {static}
}
}
RequestHandler --> "-next" RequestHandler
Request --> "-requestType" RequestType
OrcKing --> "-chain" RequestHandler
OrcCommander --|> RequestHandler
OrcOfficer --|> RequestHandler
OrcSoldier --|> RequestHandler
@enduml

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,43 @@
/**
* The MIT License
* Copyright (c) 2014 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.dao;
/**
*
* Custom exception
*
*/
public class CustomException extends Exception {
private static final long serialVersionUID = 1L;
public CustomException() {}
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -36,12 +36,16 @@ import java.util.stream.StreamSupport;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
/**
* An implementation of {@link CustomerDao} that persists customers in RDBMS.
* An implementation of {@link CustomerDao} that persists customers in RDBMS.
*
*/
public class DbCustomerDao implements CustomerDao {
private static final Logger LOGGER = Logger.getLogger(DbCustomerDao.class);
private final DataSource dataSource;
/**
@ -65,8 +69,8 @@ public class DbCustomerDao implements CustomerDao {
Connection connection;
try {
connection = getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); //NOSONAR
ResultSet resultSet = statement.executeQuery(); //NOSONAR
PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR
ResultSet resultSet = statement.executeQuery(); // NOSONAR
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Customer>(Long.MAX_VALUE,
Spliterator.ORDERED) {
@ -79,12 +83,12 @@ public class DbCustomerDao implements CustomerDao {
action.accept(createCustomer(resultSet));
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
throw new RuntimeException(e); // NOSONAR
}
}
}, false).onClose(() -> mutedClose(connection, statement, resultSet));
} catch (SQLException e) {
throw new Exception(e.getMessage(), e);
throw new CustomException(e.getMessage(), e);
}
}
@ -98,7 +102,7 @@ public class DbCustomerDao implements CustomerDao {
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
LOGGER.info("Exception thrown " + e.getMessage());
}
}
@ -113,19 +117,26 @@ public class DbCustomerDao implements CustomerDao {
*/
@Override
public Optional<Customer> getById(int id) throws Exception {
ResultSet resultSet = null;
try (Connection connection = getConnection();
PreparedStatement statement =
connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, id);
ResultSet resultSet = statement.executeQuery();
resultSet = statement.executeQuery();
if (resultSet.next()) {
return Optional.of(createCustomer(resultSet));
} else {
return Optional.empty();
}
} catch (SQLException ex) {
throw new Exception(ex.getMessage(), ex);
throw new CustomException(ex.getMessage(), ex);
} finally {
if (resultSet != null) {
resultSet.close();
}
}
}
@ -147,7 +158,7 @@ public class DbCustomerDao implements CustomerDao {
statement.execute();
return true;
} catch (SQLException ex) {
throw new Exception(ex.getMessage(), ex);
throw new CustomException(ex.getMessage(), ex);
}
}
@ -164,7 +175,7 @@ public class DbCustomerDao implements CustomerDao {
statement.setInt(3, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new Exception(ex.getMessage(), ex);
throw new CustomException(ex.getMessage(), ex);
}
}
@ -179,7 +190,7 @@ public class DbCustomerDao implements CustomerDao {
statement.setInt(1, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new Exception(ex.getMessage(), ex);
throw new CustomException(ex.getMessage(), ex);
}
}
}

View File

@ -0,0 +1,29 @@
---
layout: pattern
title: Partial Response
folder: partial-response
permalink: /patterns/partial-response/
categories: Architectural
tags:
- Java
- KISS
- YAGNI
- Difficulty-Beginner
---
## Intent
Send partial response from server to client on need basis. Client will specify the the fields
that it need to server, instead of serving all details for resource.
![alt text](./etc/partial-response.urm.png "partial-response")
## Applicability
Use the Partial Response pattern when
* Client need only subset of data from resource.
* To avoid too much data transfer over wire
## Credits
* [Common Design Patterns](https://cloud.google.com/apis/design/design_patterns)
* [Partial Response in RESTful API Design](http://yaoganglian.com/2013/07/01/partial-response/)

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.partialresponse.Video" project="partial-response"
file="/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="322" y="457"/>
<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="2" language="java" name="com.iluwatar.partialresponse.FieldJsonMapper" project="partial-response"
file="/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="772" y="412"/>
<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.partialresponse.VideoClientApp" project="partial-response"
file="/partial-response/src/main/java/com/iluwatar/partialresponse/VideoClientApp.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="215" y="125"/>
<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.partialresponse.VideoResource" project="partial-response"
file="/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="101" width="319" x="476" y="66"/>
<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="5">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="6" name="videos"/>
<multiplicity id="7" minimum="0" maximum="2147483647"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="8">
<end type="SOURCE" refId="4" navigable="false">
<attribute id="9" name="fieldJsonMapper"/>
<multiplicity id="10" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="2" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-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"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,31 @@
@startuml
package com.iluwatar.partialresponse {
class FieldJsonMapper {
+ FieldJsonMapper()
- getString(video : Video, declaredField : Field) : String
+ toJson(video : Video, fields : String[]) : String
}
class Video {
- description : String
- director : String
- id : Integer
- language : String
- length : Integer
- title : String
+ Video(id : Integer, title : String, length : Integer, description : String, director : String, language : String)
+ toString() : String
}
class VideoClientApp {
- LOGGER : Logger {static}
+ VideoClientApp()
+ main(args : String[]) {static}
}
class VideoResource {
- fieldJsonMapper : FieldJsonMapper
- videos : Map<Integer, Video>
+ VideoResource(fieldJsonMapper : FieldJsonMapper, videos : Map<Integer, Video>)
+ getDetails(id : Integer, fields : String[]) : String
}
}
VideoResource --> "-fieldJsonMapper" FieldJsonMapper
@enduml

49
partial-response/pom.xml Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License
Copyright (c) 2014 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.17.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>partial-response</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,74 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* The Partial response pattern is a design pattern in which client specifies fields to fetch to serve.
* Here {@link App} is playing as client for {@link VideoResource} server.
* Client ask for specific fields information in video to server.
* <p>
* <p>
* {@link VideoResource} act as server to serve video information.
*/
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Method as act client and request to server for video details.
*
* @param args program argument.
*/
public static void main(String[] args) throws Exception {
Map<Integer, Video> videos = new HashMap<>();
videos.put(1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"));
videos.put(2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese"));
videos.put(3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English"));
VideoResource videoResource = new VideoResource(new FieldJsonMapper(), videos);
LOGGER.info("Retrieving full response from server:-");
LOGGER.info("Get all video information:");
String videoDetails = videoResource.getDetails(1);
LOGGER.info(videoDetails);
LOGGER.info("----------------------------------------------------------");
LOGGER.info("Retrieving partial response from server:-");
LOGGER.info("Get video @id, @title, @director:");
String specificFieldsDetails = videoResource.getDetails(3, "id", "title", "director");
LOGGER.info(specificFieldsDetails);
LOGGER.info("Get video @id, @length:");
String videoLength = videoResource.getDetails(3, "id", "length");
LOGGER.info(videoLength);
}
}

View File

@ -0,0 +1,60 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
import java.lang.reflect.Field;
/**
* Map a video to json
*/
public class FieldJsonMapper {
/**
* @param video object containing video information
* @param fields fields information to get
* @return json of required fields from video
*/
public String toJson(Video video, String[] fields) throws Exception {
StringBuilder json = new StringBuilder().append("{");
for (int i = 0, fieldsLength = fields.length; i < fieldsLength; i++) {
json.append(getString(video, Video.class.getDeclaredField(fields[i])));
if (i != fieldsLength - 1) {
json.append(",");
}
}
json.append("}");
return json.toString();
}
private String getString(Video video, Field declaredField) throws IllegalAccessException {
declaredField.setAccessible(true);
Object value = declaredField.get(video);
if (declaredField.get(video) instanceof Integer) {
return "\"" + declaredField.getName() + "\"" + ": " + value;
}
return "\"" + declaredField.getName() + "\"" + ": " + "\"" + value.toString() + "\"";
}
}

View File

@ -0,0 +1,70 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
/**
* {@link Video} is a entity to serve from server.It contains all video related information..
* <p>
*/
public class Video {
private final Integer id;
private final String title;
private final Integer length;
private final String description;
private final String director;
private final String language;
/**
* @param id video unique id
* @param title video title
* @param length video length in minutes
* @param description video description by publisher
* @param director video director name
* @param language video language {private, public}
*/
public Video(Integer id, String title, Integer length, String description, String director, String language) {
this.id = id;
this.title = title;
this.length = length;
this.description = description;
this.director = director;
this.language = language;
}
/**
* @return json representaion of video
*/
@Override
public String toString() {
return "{"
+ "\"id\": " + id + ","
+ "\"title\": \"" + title + "\","
+ "\"length\": " + length + ","
+ "\"description\": \"" + description + "\","
+ "\"director\": \"" + director + "\","
+ "\"language\": \"" + language + "\","
+ "}";
}
}

View File

@ -0,0 +1,57 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
import java.util.Map;
/**
* The resource class which serves video information.
* This class act as server in the demo. Which has all video details.
*/
public class VideoResource {
private FieldJsonMapper fieldJsonMapper;
private Map<Integer, Video> videos;
/**
* @param fieldJsonMapper map object to json.
* @param videos initialize resource with existing videos. Act as database.
*/
public VideoResource(FieldJsonMapper fieldJsonMapper, Map<Integer, Video> videos) {
this.fieldJsonMapper = fieldJsonMapper;
this.videos = videos;
}
/**
* @param id video id
* @param fields fields to get information about
* @return full response if no fields specified else partial response for given field.
*/
public String getDetails(Integer id, String... fields) throws Exception {
if (fields.length == 0) {
return videos.get(id).toString();
}
return fieldJsonMapper.toJson(videos.get(id), fields);
}
}

View File

@ -0,0 +1,40 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
import org.junit.Test;
/**
* Application test
*/
public class AppTest {
@Test
public void main() throws Exception {
String[] args = {};
App.main(args);
}
}

View File

@ -0,0 +1,53 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* tests {@link FieldJsonMapper}.
*/
public class FieldJsonMapperTest {
private FieldJsonMapper mapper;
@Before
public void setUp() {
mapper = new FieldJsonMapper();
}
@Test
public void shouldReturnJsonForSpecifiedFieldsInVideo() throws Exception {
String[] fields = new String[]{"id", "title", "length"};
Video video = new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese");
String jsonFieldResponse = mapper.toJson(video, fields);
String expectedDetails = "{\"id\": 2,\"title\": \"Godzilla Resurgence\",\"length\": 120}";
assertEquals(expectedDetails, jsonFieldResponse);
}
}

View File

@ -0,0 +1,80 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Gopinath Langote
*
* 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.partialresponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
/**
* tests {@link VideoResource}.
*/
@RunWith(MockitoJUnitRunner.class)
public class VideoResourceTest {
@Mock
private FieldJsonMapper fieldJsonMapper;
private VideoResource resource;
@Before
public void setUp() {
Map<Integer, Video> videos = new HashMap<>();
videos.put(1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"));
videos.put(2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese"));
videos.put(3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English"));
resource = new VideoResource(fieldJsonMapper, videos);
}
@Test
public void shouldGiveVideoDetailsById() throws Exception {
String actualDetails = resource.getDetails(1);
String expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": "
+ "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\",}";
assertEquals(expectedDetails, actualDetails);
}
@Test
public void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception {
String[] fields = new String[]{"id", "title", "length"};
String expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178}";
when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails);
String actualFieldsDetails = resource.getDetails(2, fields);
assertEquals(expectedDetails, actualFieldsDetails);
}
}

View File

@ -145,8 +145,9 @@
<module>cqrs</module>
<module>event-sourcing</module>
<module>data-transfer-object</module>
<module>throttling</module>
<module>throttling</module>
<module>unit-of-work</module>
<module>partial-response</module>
</modules>
<dependencyManagement>
@ -474,6 +475,8 @@
<param>decorator</param>
<param>facade</param>
<param>flyweight</param>
<param>proxy</param>
<param>chain</param>
</skipForProjects>
</configuration>
</plugin>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.11" icons="true" automaticImage="PNG" always-add-relationships="false"
generalizations="true" realizations="true" associations="true" dependencies="false" nesting-relationships="true"
router="FAN">
<class id="1" language="java" name="com.iluwatar.proxy.WizardTowerProxy" project="proxy"
file="/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="149" width="191" x="388" y="271"/>
<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="2" language="java" name="com.iluwatar.proxy.Wizard" project="proxy"
file="/proxy/src/main/java/com/iluwatar/proxy/Wizard.java" binary="false" corner="BOTTOM_RIGHT">
<position height="113" width="102" x="619" y="271"/>
<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="3" language="java" name="com.iluwatar.proxy.WizardTower" project="proxy"
file="/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java" binary="false" corner="BOTTOM_RIGHT">
<position height="77" width="116" x="388" y="460"/>
<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="4" language="java" name="com.iluwatar.proxy.IvoryTower" project="proxy"
file="/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java" binary="false" corner="BOTTOM_RIGHT">
<position height="113" width="116" x="761" y="271"/>
<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.proxy.App" project="proxy"
file="/proxy/src/main/java/com/iluwatar/proxy/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="95" width="114" x="917" y="271"/>
<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="6">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="7" name="tower"/>
<multiplicity id="8" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="3" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="9">
<end type="SOURCE" refId="4"/>
<end type="TARGET" refId="3"/>
</realization>
<realization id="10">
<end type="SOURCE" refId="1"/>
<end type="TARGET" refId="3"/>
</realization>
<classifier-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"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

View File

@ -1,32 +0,0 @@
@startuml
package com.iluwatar.proxy {
class App {
+ App()
+ main(args : String[]) {static}
}
class IvoryTower {
- LOGGER : Logger {static}
+ IvoryTower()
+ enter(wizard : Wizard)
}
class Wizard {
- name : String
+ Wizard(name : String)
+ toString() : String
}
interface WizardTower {
+ enter(Wizard) {abstract}
}
class WizardTowerProxy {
- LOGGER : Logger {static}
- NUM_WIZARDS_ALLOWED : int {static}
- numWizards : int
- tower : WizardTower
+ WizardTowerProxy(tower : WizardTower)
+ enter(wizard : Wizard)
}
}
WizardTowerProxy --> "-tower" WizardTower
IvoryTower ..|> WizardTower
WizardTowerProxy ..|> WizardTower
@enduml