Thread Injection Http Server Tutorial
This tutorial demonstrates the ability to assign a thread pool to execute specific methods (i.e. thread injection). OfficeFloor refers to a thread pool as a Team.
The example used for this tutorial is an encrypter for a letter. A database table stores the encryption by mapping the letter to an alternate letter. Once the letter encryption is looked up in the database it is cached to provide quicker subsequent lookups for that letter. The example's web page is as follows and has been kept simple to focus on thread injection.

The example displays the names of two threads to show the different thread pools (Teams) servicing the request.
The thread per request architecture of most web servers will typically have a single thread pool to service all requests. Database I/O is typically blocking. Under heavy loads all threads can end up blocked waiting on the database. As the web server has no free threads, it queues further requests degrading the responsiveness of the web application.
Most requests to a web server are for static cached content. Having these requests wait behind longer database requests means non-optimal use of the web server. The blocked threads leaves the web server's CPU idle. Having an additional thread would allow the web server to continue servicing the static cached content requests. This is especially relevant as the company's home page is usually static cached content and users have a low tolerance for slow web site responsiveness. Also increasing the number of threads for cached content can cause lock contention and potential stavation resulting in degraded responsiveness.
Serving cached content is best by a single thread while database content is best by a pool of threads. These two types of request are typically handled by most web servers with a single thread pool. However increasing the efficiency and responsiveness of a web server is not acheived by increasing the number of threads but by increasing the number of thread pools.
The example for this tutorial shows how OfficeFloor enables increasing the number of thread pools for efficient servicing of cached and database requests.
The following is the application.woof configuration for the example application.

The DataSource to the database is configured as follows.
<objects> <managed-object source="net.officefloor.plugin.jdbc.datasource.DataSourceManagedObjectSource" type="javax.sql.DataSource"> <property-file path="datasource.properties" /> </managed-object> </objects>
With the following properties.
data.source.class.name=org.hsqldb.jdbc.jdbcDataSource database=jdbc:hsqldb:mem:exampleDb user=sa
See the other tutorials for explanation of the above configuration.
The thread injection configuration is contained in the application.teams file at the root of the class path. For the example application it is as follows.
<teams> <team source="net.officefloor.frame.impl.spi.team.LeaderFollowerTeamSource" type="javax.sql.DataSource"> <property name="size" value="10" /> </team> </teams>
The leader follower team is a TeamSource implementation that implements the leader follower thread pool pattern to reduce lock contention.
WoOF uses the method's dependencies to determine the type of functionality being undertaken by the method. The method's dependencies give an indicator of what the method is likely to be doing. In this case if the method requires a DataSource it is very likely that it will be doing blocking database I/O calls. This means of classifying methods allows OfficeFloor to automagically use the appropriate thread pool to execute the method. It is what OfficeFloor considers thread injection.
All other methods are executed by the default Team. Running stand-alone this would be the OnePersonTeamSource which is a single threaded team.
Adding thread pools is optional and therefore the inclusion of the application.teams file is optional. It is anticipated that threading will be configured closely to the dependencies available within an environment. In many cases this will be handled by the environment deployment. The file however is supported for extending WoOF web applications by customising the thread pools.
The following is the content of the template.
<html>
<body>
<form action="#{encrypt}">
Letter: <input name="letter" value="${letter}" type="text" maxlength="1" />
<input type="submit" value="Code" />
</form>
<p>Encrypted code: ${code}</p>
<br />
<!-- {ThreadNames} -->
<p>Cache thread: ${cacheThreadName}</p>
<p>Database I/O thread: ${databaseThreadName}</p>
</body>
</html>
The following provides the values for the ${property} entries from the template logic.
@HttpSessionStateful
public class Template implements Serializable {
private Map<Character, LetterEncryption> cache = new HashMap<Character, LetterEncryption>();
private LetterEncryption displayCode;
private String cacheThreadName;
private String databaseThreadName;
public LetterEncryption getTemplate() {
return this.displayCode;
}
public Template getThreadNames() {
return this;
}
public String getCacheThreadName() {
return this.cacheThreadName;
}
public String getDatabaseThreadName() {
return this.databaseThreadName;
}
Along with providing the values the class is also annotated so that it is stored within the HTTP session. This allows the cache field to act as a cache across requests. See the other tutorials for further details.
The example application first tries the cache for the encrypted code.
@FlowInterface
public static interface PageFlows {
void retrieveFromDatabase(char letter);
}
@NextTask("setDisplayCode")
public LetterEncryption encrypt(EncryptLetter request, PageFlows flows) {
// Specify thread name (clearing database thread)
this.cacheThreadName = Thread.currentThread().getName();
this.databaseThreadName = "[cached]";
// Obtain from cache
char letter = request.getLetter();
LetterEncryption code = this.cache.get(new Character(letter));
if (code != null) {
return code;
}
// Not in cache so retrieve from database
flows.retrieveFromDatabase(letter);
return null; // for compiler
}
public void setDisplayCode(@Parameter LetterEncryption encryption) {
this.displayCode = encryption;
}
Should the encrypted code be found in the cache it is passed as a parameter to the setter method. The setter method keeps reference to the encrypted code for rendering the web page response. See the other tutorials for explanation of this WoOF functionality.
On not finding the encrypted code in the cache the above method triggers for it to be retrieved from the database. The following method retrieves the encrypted code from the database. The returned encrypted code is passed to the setter method for rendering to the web page.
@NextTask("setDisplayCode")
public LetterEncryption retrieveFromDatabase(@Parameter char letter,
DataSource dataSource) throws SQLException {
// Specify thread name
this.databaseThreadName = Thread.currentThread().getName();
// Retrieve from database and cache
Connection connection = dataSource.getConnection();
try {
PreparedStatement statement = connection
.prepareStatement("SELECT CODE FROM LETTER_CODE WHERE LETTER = ?");
statement.setString(1, String.valueOf(letter));
ResultSet resultSet = statement.executeQuery();
resultSet.next();
String code = resultSet.getString("CODE");
LetterEncryption letterCode = new LetterEncryption(letter,
code.charAt(0));
// Cache
this.cache.put(new Character(letter), letterCode);
return letterCode;
} finally {
connection.close();
}
}
As the method has a DataSource dependency it is executed by the leader follower team. This is reflected by the web page response showing different threads executing the cache and database methods.
Though this is a simple example it does highlight that under heavy load that cached letter encryptions can still be serviced even if all database (leader follower) threads are blocked waiting on the database.
For completeness of the tutorial the remaining code for the example application is included below.
public class Setup {
public void setupDatabase(DataSource dataSource) throws SQLException {
Connection connection = dataSource.getConnection();
try {
connection
.createStatement()
.execute(
"CREATE TABLE LETTER_CODE ( LETTER CHAR(1) PRIMARY KEY, CODE CHAR(1) )");
PreparedStatement statement = connection
.prepareStatement("INSERT INTO LETTER_CODE ( LETTER, CODE ) VALUES ( ?, ? )");
for (char letter = ' '; letter <= 'z'; letter++) {
char code = (char) ('z' - letter + ' '); // simple reverse order
statement.setString(1, String.valueOf(letter));
statement.setString(2, String.valueOf(code));
statement.execute();
}
} finally {
connection.close();
}
}
}
The following unit test makes requests to encrypt a letter.
public void testRetrieveEncryptions() throws Exception {
// Request page to allow time for database setup
this.doRequest("http://localhost:7878/example");
// Retrieving from database
this.doRequest("http://localhost:7878/example.links-encrypt.task?letter=A");
// Looking up within cache
this.doRequest("http://localhost:7878/example.links-encrypt.task?letter=A");
}
private void doRequest(String url) throws Exception {
HttpResponse response = this.client.execute(new HttpGet(url));
response.getEntity().writeTo(System.out);
assertEquals("Request should be successful", 200, response
.getStatusLine().getStatusCode());
}
As the same letter is requested, the