My Adventures in Coding

April 13, 2016

SQL Server – Get Sizes of All Tables and Indexes in a Database

Filed under: SQL,SQL Server — Brian @ 9:51 pm
Tags: , , , ,

Even though there are great tools in SQL Server Management Studio that give you a wealth of information about all of your tables and indexes, I still find it handy to have a script I can use to quickly check the current state of all tables and indexes in a database.

Size of each Table (Including Indexes)

This query gives you a total size for each table in KB including all of the indexes on that table. The query shows the table name, row count, total space, and total space used by the table and its indexes.

SELECT
    t.[Name] AS TableName,
    p.[rows] AS [RowCount],
    SUM(a.total_pages) * 8 AS TotalSpaceKB,
    SUM(a.used_pages) * 8 AS UsedSpaceKB
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
WHERE t.is_ms_shipped = 0 AND i.OBJECT_ID > 255
GROUP BY t.[Name], p.[Rows]
ORDER BY t.[Name]

Size of each Index

This query shows the total size in KB of each index in the database. The query shows the name of each index, which table the index is on, and the total size of the index.

SELECT 
    i.[name] AS IndexName,
    t.[name] AS TableName,
    SUM(s.[used_page_count]) * 8 AS IndexSizeKB
FROM sys.dm_db_partition_stats AS s
INNER JOIN sys.indexes AS i ON s.[object_id] = i.[object_id] 
	AND s.[index_id] = i.[index_id]
INNER JOIN sys.tables t ON t.OBJECT_ID = i.object_id
GROUP BY i.[name], t.[name]
ORDER BY i.[name], t.[name]

I hope you find these queries useful!

April 3, 2016

Jetty – Setup Jetty on CentOS 7

Filed under: Jetty,Linux — Brian @ 9:50 pm
Tags: , , , ,

I recently setup Jetty on a CentOS 7 Linux server. Previously we had been running Jetty in a windows environment but now are moving our servers over to Linux. Here are my setup notes, I hope they help!

Install JDK

Note: We are using the Oracle JDK (Not openJDK). Check for the latest version of JDK 1.8.

Download and install the JDK:

wget --no-cookies --no-check-certificate --header "Cookie: oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u73-b02/jdk-8u73-linux-x64.rpm" -O /opt/jdk-8-linux-x64.rpm

yum install /opt/jdk-8-linux-x64.rpm

Set JAVA_HOME and add it to your PATH:
vi /etc/profile

Add the lines:

export JAVA_HOME=/usr/java/jdk1.8.0_73
export PATH=$JAVA_HOME/bin:$PATH

Install Jetty Web Server

Note: Check for the latest version of Jetty.

Download and install Jetty:

wget http://download.eclipse.org/jetty/stable-9/dist/jetty-distribution-9.3.8.v20160314.tar.gz

tar -zxvf jetty-distribution-9.3.8.v20160314.tar.gz

mv jetty-distribution-9.3.8.v20160314 /opt/jetty

Create a temp folder for Jetty to unpack war files:

mkdir /opt/jetty/temp

Create webappbase folder where your apps will run:

mkdir /opt/jetty/webappbase

mkdir /opt/jetty/webappbase/logs

Move start.ini and webapps into the webappbase folder:

mv /opt/jetty/webapps /opt/jetty/webappbase/webapps

mv /opt/jetty/start.ini /opt/jetty/webappbase

Create a “Jetty” user that Jetty will run under:

useradd -m jetty 

chown -R jetty:jetty /opt/jetty

Link Jetty startup script to /etc/init.d

ln -s /opt/jetty/bin/jetty.sh /etc/init.d/jetty

Add the new Jetty service to be managed by chkconfig:

chkconfig --add jetty

Set Jetty service to run on startup for the following run levels:

chkconfig --level 345 jetty on

Configure Jetty Web Server

You will need to set TMPDIR, JETTY_BASE, and JETTY_HOME. Also, any JAVA_OPTIONS you need to set can be set in the “jetty.sh” file.

To update your Jetty configuration add these lines to the top of the jetty.sh file:
vi /opt/jetty/bin/jetty.sh

TMPDIR=/opt/jetty/temp
JETTY_BASE=/opt/jetty/webappbase
JETTY_HOME=/opt/jetty
JAVA_OPTIONS="-Xms128m -Xmx2048m -server -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled"

If you want to change the default port number, you can do it by editing the start.ini file:
vi /opt/jetty/webappbase/start.ini

# Replace 
# jetty.http.port=8080
# With 
jetty.http.port=8081

Start Jetty Server

Switch to the “Jetty” user, then start the Jetty service:

sudo su - jetty
service jetty start

Test that the server is running:

curl localhost:8081

Deploy a War file

To deploy an application to Jetty, all you need to do is copy a WAR file to the folder:

/opt/jetty/webappbase/webapps

That’s all!

January 5, 2016

SQL Server – How to write an Upsert using MERGE

Filed under: SQL,SQL Server — Brian @ 8:35 pm
Tags: , , , ,

Normally, when you want an application to INSERT a row to a table if it does not exist or UPDATE it if it does exist, your application must first do a SELECT to check if the row exists, which is the standard SELECT-UPDATE-INSERT pattern. The down side to this pattern is it requires two database round trips instead of one.

Now, over the years I have worked with document stores such as MongoDB and really enjoyed the freedom to be able to make a single call to the database and be able to ADD/REPLACE a document, without having to check if it exists.

Fortunately in SQL Server 2008, the MERGE function was introduced.

MERGE allows you to make only a single database round trip when you want to INSERT a row if it does not exist, or UPDATE it if it does. The following is a simple example showing how to use the MERGE statement.

Quick Start

So if you just want a quick example to get you started then here you go. In this example the table “ClientData” is the one being updated.

  • MERGE – specifies the table we will be inserting a row into or updating
  • USING – defines the condition we will be using to check if the row exists or not
  • WHEN MATCHED THEN – SQL statement to run when the row exists
  • WHEN NOT MATCHED – SQL statement to run when the row does not exist
MERGE dbo.ClientData AS [Target] 
USING (SELECT 12345 AS clientId, 'Some' AS data) AS [Source] ON [Target].clientId = [Source].clientId 
WHEN MATCHED THEN UPDATE SET [Target].data = [Source].data, [Target].updatedDateUtc = GetUtcDate() 
WHEN NOT MATCHED THEN INSERT (clientId, data) VALUES ([Source].clientId, [Source].data);

How it Works

First lets create a table to use for our test of the Merge statement:

CREATE TABLE dbo.ClientData(
	ClientId [bigint] NOT NULL,
	Data [varchar](20) NOT NULL,
	UpdatedDateUtc [datetime] NOT NULL DEFAULT (getutcdate()),
 CONSTRAINT [PK_ClientData_ClientId] PRIMARY KEY CLUSTERED (
	ClientId ASC
)) ON [PRIMARY]
GO

You can verify the table has been created and see that it is empty:

SELECT * FROM dbo.ClientData

Now, run the following Merge statement for the first time, where no matching row in the table:

MERGE dbo.ClientData AS [Target]
USING (SELECT 12345 AS clientId) AS [Source] 
ON [Target].clientId = [Source].clientId
WHEN MATCHED THEN  UPDATE SET [Target].data = 'Update', [Target].updatedDateUtc = GetUtcDate()
WHEN NOT MATCHED THEN  INSERT (clientId, data) VALUES ([Source].clientId, 'Insert');

As you can see, the INSERT statement was executed:

SELECT * FROM dbo.ClientData

Now let’s run the exact same merge statement a second time and see what happens:

MERGE dbo.ClientData AS [Target]
USING (SELECT 12345 AS clientId) AS [Source] 
ON [Target].clientId = [Source].clientId
WHEN MATCHED THEN  UPDATE SET [Target].data = 'Update', [Target].updatedDateUtc = GetUtcDate()
WHEN NOT MATCHED THEN  INSERT (clientId, data) VALUES ([Source].clientId, 'Insert');

Now, you can see the the UPDATE statement was executed since the “data” field has been updated to the text “Update”:

select * from dbo.ClientData

If you are curious about the performance difference between MERGE and SELECT-INSERT-UPDATE here is a performance comparison.

January 2, 2016

Java – Simple GZIP Utility to Compress and Decompress a String

Filed under: Java — Brian @ 10:15 pm
Tags: , , ,

I wanted to have a simple utility class in our app so we could easily compress a String to a byte[] when our REST API received a GET request with the header “Accept-Encoding: gzip” and also be able to decompress a byte[] to a String when we received a PUT with the header “Content-Encoding: gzip”. So I wrote a simple utility class to GZIP a String to a byte[] and to unzip a GZIP byte[] to a String.

So here is a simple GzipUtil class:

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class GzipUtil {

    public static byte[] zip(final String str) {
        if ((str == null) || (str.length() == 0)) {
            throw new IllegalArgumentException("Cannot zip null or empty string");
        }

        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
                gzipOutputStream.write(str.getBytes(StandardCharsets.UTF_8));
            }
            return byteArrayOutputStream.toByteArray();
        } catch(IOException e) {
            throw new RuntimeException("Failed to zip content", e);
        }
    }

    public static String unzip(final byte[] compressed) {
        if ((compressed == null) || (compressed.length == 0)) {
            throw new IllegalArgumentException("Cannot unzip null or empty bytes");
        }
        if (!isZipped(compressed)) {
            return new String(compressed);
        }

        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressed)) {
            try (GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream)) {
                try (InputStreamReader inputStreamReader = new InputStreamReader(gzipInputStream, StandardCharsets.UTF_8)) {
                    try (BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
                        StringBuilder output = new StringBuilder();
                        String line;
                        while((line = bufferedReader.readLine()) != null){
                            output.append(line);
                        }
                        return output.toString();
                    }
                }
            }
        } catch(IOException e) {
            throw new RuntimeException("Failed to unzip content", e);
        }
    }

    public static boolean isZipped(final byte[] compressed) {
        return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8));
    }
}

Also here are a set of JUnit tests for this utility:

import org.junit.Test;
import static org.junit.Assert.*;

public class GzipUtilTest {

    @Test(expected = IllegalArgumentException.class)
    public void zip_shouldThrowIllegalArgumentException_whenStringToCompressIsNull() {
        GzipUtil.zip(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void zip_shouldThrowIllegalArgumentException_whenStringToCompressIsEmpty() {
        GzipUtil.zip("");
    }

    @Test
    public void zip_shouldGzipString_whenStringIsNotEmpty() {
        String xml = "<Hello>World</Hello>";

        byte[] actual = GzipUtil.zip(xml);

        assertTrue(GzipUtil.isZipped(actual));
    }

    @Test(expected = IllegalArgumentException.class)
    public void unzip_shouldThrowIllegalArgumentException_whenByteArrayToDecompressIsNull() {
        GzipUtil.unzip(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void unzip_shouldThrowIllegalArgumentException_whenByteArrayToDecompressIsEmpty() {
        GzipUtil.unzip(new byte[0]);
    }

    @Test
    public void unzip_shouldReturnInputByteArrayAsString_whenByteArrayContentIsNotGzipped() {
        String xml = "<Hello>World</Hello>";
        byte[] bytes = xml.getBytes();

        String actual = GzipUtil.unzip(bytes);

        assertEquals(xml, actual);
    }

    @Test
    public void unzip_shouldDecompressByteArrayGzippedContent() throws Exception {
        String xml = "<Hello>World</Hello>";
        byte[] compressed = GzipUtil.zip(xml);

        String actual = GzipUtil.unzip(compressed);

        assertEquals(xml, actual);
    }

    @Test
    public void isZipped_shouldReturnFalse_whenContentIsNotGzipped() {
        byte[] bytes = new byte[] {1,2,3};

        boolean actual = GzipUtil.isZipped(bytes);

        assertFalse(actual);
    }

    @Test
    public void isZipped_shouldReturnTrue_whenContentIsGzipped() {
        byte[] bytes = GzipUtil.zip("1,2,3");

        boolean actual = GzipUtil.isZipped(bytes);

        assertTrue(actual);
    }
}

I hope you find this useful!

August 14, 2015

Java – Automate database schema updates with Flyway

Filed under: Java — Brian @ 1:44 pm
Tags: , , , , , ,

I am currently working on a Java 8 project which is a REST API deployed as a WAR file to Jetty. Our deploy process is very simple, our deploy pipeline just copies the WAR file into the Jetty directory in each environment then verifies the app is up and running with the correct version and runs some integration tests.

We wanted to be able to apply database migration scripts automatically in each environment (Dev, Test, QA, Staging, Prod) as we did our deploy, so we would no longer have to worry about manually applying scripts. In the past for Java, Scala, and .NET projects I have used several different tools, but for this project we decided to use Flyway which is very flexible and simple to setup.

The documentation for Flyway is excellent, however I decided to just post what we did in our app in case it might help someone else out. Here is our “Quick Start” setup.

1. Add the Flyway dependency

Flyway can be setup using Maven, Gradle, SBT, Ant, etc. In our project we used Maven, so all we did was add the following to our pom.xml file:

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>3.2.1</version>
</dependency>

2. Code

Now, to get Flyway to check for any database changes to apply, whenever the application is started, put the following code somewhere in your applications startup sequence.

Flyway flyway = new Flyway();
//Create the dbo.schema_version table if it does not already exist
flyway.setBaselineOnMigrate(true);
//Where the method "getDataSource()" provides your DataSource 
//object that has the jdbc url, username, and password.
flyway.setDataSource(getDataSource()); 
flyway.migrate();

3. SQL Script Location

All migrations scripts by default must go in the following folder in your app:

/resources/db/migration

4. SQL Script Naming convention

Scripts are run in version number order based on their names, the default naming convention is:

Version__description.sql
For example: “V1_0_1__create_tables.sql”

All scripts must start with the letter “V”, followed by major/minor version numbers, with two underscores “__” separating the version from the description.

5. Schema Version Table

Flywaydb will automatically create a table in each database called “dbo.schema_version” which stores a log of all migration scripts that have been applied.

The table looks like:

version_rank installed_rank version description type script checksum installed_by installed_on execution_time success
1 1 1 Flyway Baseline BASELINE Flyway Baseline NULL JohnSmith 2015-07-30 15:55:49.337 0 1
1 2 1.0.1 create tables SQL V1_0_1__create_tables.sql -440358290 JohnSmith 2015-07-30 15:55:49.337 109 1

6. Handling Failure

If a script fails, the app will fail to start and the failure information is written to our log files as well as a monitor is tripped. Flywaydb applies each script as a transaction so all changes in the script will be rolled back if any part of the script fails. This is very handy because if you commit a script with some invalid SQL syntax, all you have to do is update the script with the corrected syntax, commit it again, and let the build pipeline apply the changes from the fixed script. No messy cleanup or reset to worry about.

So that is it, you should have all you need to get your database schema changes easily synced up with the deploy of your app!

June 7, 2015

Console2 – Mac(ish) terminal on Windows using Console2 and UnixUtils

Filed under: Windows — Brian @ 6:52 pm
Tags: , , , , ,

If you are like me and you are used to using a terminal window on a Mac, using the Windows command prompt can be a bit frustrating. I like having basic functionality like tabs, short cuts to switch tabs, cut and paste with Ctrl+c and Ctrl+v, dragging the corner of a terminal to resize, all of which I take for granted on my Mac. Fortunately, Console2 and UnixUtils can help (with a little customization).

Console2 is an open source project that provides a tabbed version of the windows console app. However, it is much more than that. Even though the default settings are less than desirable, Console2 is highly customizable. So you can easily change the defaults to have all kinds of amazing features such as “select to copy”, Ctrl+c to copy, Ctrl+v to paste, drag the corner of the window to resize it, short cuts to open new tabs, switch tabs, etc. Yep, you can make it function like a normal command prompt!

Download Console2

Download the zip file, extract it, and run Console2.exe.

Console2

So far it does not look like much of an improvement, but don’t worry, we have some customizing to do.

Go to Edit -> Settings

“Console” – set the Buffer size rows to something larger, such as 9999:
2-GeneralConsoleSettings

“Appearance” – customize the font, font size, and text colour. I like Consolas, 12 point font, and a nice green text colour to remind me of the default on my Mac.

3-SetFontTypeSizeAndColour

“Appearance -> More” – hide the toolbar and status bar, ensure tabs are displayed, and set some cool transparency.

4-Appearance-More

“Behaviour” – ensure “Copy on select” is checked. This will allow you to select text on the console window and have it automatically put on the clipboard (Woohoo!).

5-Behaviour-CopyOnSelect

“Hotkeys” – set the “New Tab 1” hotkey to use Ctrl+T.

6-HotKeys-SetNewTab

“Hotkeys” – set “Copy Selection” to Ctrl+c.
7-HotKeys-SetCopy

“Hotkeys” – set “Paste” to Ctrl+v.
8-HotKeys-SetPaste

“Hotkeys -> Mouse” – set “Copy/clear selection” to “None”.
9-HotKeys-Mouse-SetCopySelection

“Hotkeys -> Mouse” – set “Select text” to “Left”.
10-HotKeys-Mouse-SetSelectText

“Hotkeys -> Mouse” – set “Paste” to “Right”.
11-HotKeys-Mouse-SetPasteText

“Hotkeys -> Mouse” – set “Context menu” to “None”.
12-HotKeys-Mouse-SetContextMenu

“Tabs” – select “Console2” and set style to “XTerm”.
13-Tabs-Console2-XTerm

Now, here is where we get to the really cool part of Console2. Besides just having a windows console tab, you can also create tabs for any other console apps such as Cygwin, Gitbash, and PowerShell.

To show you how to setup a second type of command prompt, lets use PowerShell as an example.

“Tabs” – select “Add”
14-Tabs-AddNew

Enter a name for the new Console window (“PowerShell”), set the path to the PowerShell executable, and add an icon.
— %SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe

15-Tabs-AddPowerShellTab

Now you will see both “Console2” and “PowerShell” in the list of types of available tabs.

Of course, also install UnixUtils as well to make your life a little easier. To install UnixUtils, all you have to do is download the zip file, unzip it somewhere, such as “C:\UnxUtils” then add to your path “C:\UnxUtils\bin”. That is all, then you should have all of your favorite commands such as “ls” and “grep”.

Finally, a somewhat functional command prompt!

16-Done

Enjoy!

February 6, 2015

IIS – PowerShell script to add IIS URL Rewrite Rule

Filed under: IIS — Brian @ 11:50 am
Tags: , , , , , , , ,

We have a Java REST API that runs on the same server as a .NET application. We only allow port 80 to be open on our web server. For this reason we needed to create a URL Rewrite Rule in IIS to redirect external traffic to the REST API on port 80 to port 81. It is fairly easy to do this setup in IIS, however we always want to have all of our server setup scripted. So here is a simple PowerShell script to create a URL Rewrite rule.

Install Application Request Routing (ARR)

Powershell Script to add Rule

In this example the application is called “fddapi” and the root of the application path is “/fddapi” which is the pattern we are looking for in the URL Rewrite rule.

$site = "iis:\sites\Default Web Site"
$filterRoot = "system.webServer/rewrite/rules/rule[@name='fddapi$_']"
Clear-WebConfiguration -pspath $site -filter $filterRoot
Add-WebConfigurationProperty -pspath $site -filter "system.webServer/rewrite/rules" -name "." -value @{name='fddapi' + $_ ;patternSyntax='Regular Expressions';stopProcessing='False'}
Set-WebConfigurationProperty -pspath $site -filter "$filterRoot/match" -name "url" -value "(fddapi)/.*"
Set-WebConfigurationProperty -pspath $site -filter "$filterRoot/conditions" -name "logicalGrouping" -value "MatchAny"
Set-WebConfigurationProperty -pspath $site -filter "$filterRoot/action" -name "type" -value "Rewrite"
Set-WebConfigurationProperty -pspath $site -filter "$filterRoot/action" -name "url" -value "http://localhost:8081/{R:0}"

After running the script your new URL Rewrite rule should now be created with the following settings:EditInBoundRule

 

So now any request to http://localhost/fddapi/someroute will be automatically redirected by IIS to our Java REST API (running on Jetty) to http://localhost:8081/fddapi/someroute and the caller will never know the difference!

January 11, 2015

Java to Sql Server – Cannot create PoolableConnectionFactory

Filed under: Java,SQL Server,Uncategorized — Brian @ 11:14 pm
Tags: , ,

If you are connecting a Java application to a SQL Server database, which is up and running, but your connection fails with the following error:

Could not acquire a connection from DataSource – Cannot create PoolableConnectionFactory

The error is most likely that the windows service “SQL Server Browser” is disabled.

To fix the problem:

  • Start -> Control Panel -> Systems & Security -> Administrative tools -> Services
  • SQL Server Browser -> right click -> properties

image2014-10-30 19-17-34

  • Set Startup type to “Automatic”
  • Apply -> Start

image2014-10-30 19-19-16

Now you should be able to connect to SQL Server from your Java application!

IIS – Setup IIS as a proxy to Jetty

Filed under: .NET,IIS — Brian @ 12:35 am
Tags: , ,

On my current project we are working on two applications for a customer, one is in .NET and the other is in Java. The client only wants to have port 80 and 443 open on the server, however, we will need to have two web servers running, IIS for the .NET application and Jetty for the Java application. Our solution was to run the .NET application on IIS on port 80 and the Java application on Jetty on port 81, then have IIS route traffic for the Java application coming in on port 80 to port 81 using IIS as a proxy to Jetty. The following is a simple tutorial on how to setup IIS as a proxy to Jetty.

Install Application Request Routing (ARR)

Setup IIS to Jetty Redirect

For this example the route for the Jetty application will be “fddapi”.

Open IIS and on the Server select “Application Request Routing” icon.

1-Open_AAR_from_Default_Website

Select “Server Proxy Settings”.

2-Server_Proxy_Settings

Set “Enable Proxy” setting and “Apply” the change.

3-Enable_Proxy_Setting

Go to “Default Web Site”, select “URL Rewrite”.

4-Select Url ReWrite

Under the Actions menu select “Add Rules”.

5-Add_Rule

Under “Inbound Rules” select “Blank Rule” and click “Ok”.

6-Blank_Rule

Edit the Inbound Rule with a pattern to match on and how to rewrite the url.

7-Edit_Inbound_Rule

Test the Setup

Now open a browser and go to the url: http://localhost/fddapi/someroute which should work now without needing to specify port 8081.

July 30, 2014

Java – Creating a simple retry command with function passing in Java 8

Filed under: Java — Brian @ 8:45 am
Tags: , , ,

Recently we have been working on an application that imports data from a number of different sources, where the network connection between us and each of these sources is not very reliable. So in our Gateway that makes these REST calls I wanted to be able to write a reusable piece of code that we could use in different calls, that in the event of a failure, would retry the command a few more times before finally giving up.

Java 7
I wanted to be able to write this retry logic and error handling code in one place and use it for a number of different method calls. There are several ways to do this, but previously in Java 7 I would have just written an abstract class with a single abstract method such as:

public abstract class RetryCommand<T> {
    private int maxRetries;

    public RetryCommand(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    // This abstract command is the method that will be implemented 
    public abstract T command();

    public final T run() throws RuntimeException {
        try {
            return command();
        } catch (Exception e) {
            return retry();
        }
    }

    private final T retry() throws RuntimeException {
        System.out.println("FAILED - Command failed, will be retried " + maxRetries + " times.");
        int retryCounter = 0;
        while (retryCounter < maxRetries) {
            try {
                return command();
            } catch (Exception e) {
                retryCounter++;
                System.out.println("FAILED - Command failed on retry " + retryCounter + " of " + maxRetries + " error: " + ex );
                if (retryCounter >= maxRetries) {
                    System.out.println("Max retries exceeded.");
                    break;
                }
            }
        }
        throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
    }
}

Then in my Gateway code, for each method that I want to wrap with my retry logic I would just do the following:

public class MyGateway {
    private RetryCommand<String> retryCommand;
    public MyGateway(int maxRetries) {
        retryCommand = new RetryCommand<>(maxRetries);
    }

    // Inline create an instance of  the abstract class RetryCommand
    // Define the body of the "command" method
    // Execute the "run" method and return the result
    public String getThing(final String id) {
        return new RetryCommand<String>() {
            public String command() {
                return client.getThatThing(id);
            }
        }.run();
    }
}

The reason for this layout was I could not pass a function as a parameter to a method, like I have done in Scala, Python, and C#. However, now that we have Java 8, we can finally pass functions as parameters using the handy features in java.util.function package!

Java 8
Java 8 uses Functional Interfaces, which are interfaces with a single abstract method. The package java.util.function defines a number of standard functional interfaces, so most of the time you will be able to use one of these. Some example functional interfaces are Function (function with return value and input param), Supplier (function with return value but no input param), and Consumer (function with input param but no return value). However, if one of these standard functional interfaces does not meet your needs you can always define your own. In the following example I used Supplier.

So now in Java 8 I would create a new RetryCommand class that has a “run” method which takes in a function:

import java.util.function.Supplier;

public class RetryCommandJava8<T> {
    private int retryCounter;
    private int maxRetries;

    public RetryCommandJava8(int maxRetries)
    {
        this.maxRetries = maxRetries;
    }

    // Takes a function and executes it, if fails, passes the function to the retry command
    public T run(Supplier<T> function) {
        try {
            return function.get();
        } catch (Exception e) {
            return retry(function);
        }
    }

    public int getRetryCounter() { return retryCounter; }

    private T retry(Supplier<T> function) throws RuntimeException {
        System.out.println("FAILED - Command failed, will be retried " + maxRetries + " times.");
        retryCounter = 0;
        while (retryCounter < maxRetries) {
            try {
                return function.get();
            } catch (Exception ex) {
                retryCounter++;
                System.out.println("FAILED - Command failed on retry " + retryCounter + " of " + maxRetries + " error: " + ex );
                if (retryCounter >= maxRetries) {
                    System.out.println("Max retries exceeded.");
                    break;
                }
            }
        }
        throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
    }
}

So now in my gateway code, I would create my fancy new retry command executer:

public class MyGateway {
    private RetryCommandJava8<String> retryCommandJava8;
    public MyGateway(int maxRetries) {
        retryCommandJava8 = new RetryCommandJava8<>(maxRetries);
    }

    // Passing function using a Lamba expression 
    public String getThing(final String id) {
        return retryCommandJava8.run(() -> client.getThatThing(id));
    }
}

Also, this setup is fairly easy to unit test, here are some example tests:

import junit.framework.TestCase;

public class RetryCommandJava8Test extends TestCase {

    public String SUCCESS = "success";
    public int MAXRETRIES = 3;
    public int SECONDSTOWAIT = 0;
    RetryCommandJava8<String> retryCommandJava8;

    public void testRetryCommandShouldNotRetryCommandWhenSuccessful() {
        retryCommandJava8 = new RetryCommandJava8<>(MAXRETRIES, SECONDSTOWAIT);

        String result = retryCommandJava8.run(() -> SUCCESS);

        assertEquals(SUCCESS, result);
        assertEquals(0, retryCommand.getRetryCounter());
    }

    public void testRetryCommandShouldRetryOnceThenSucceedWhenFailsOnFirstCallButSucceedsOnFirstRetry() {
        retryCommand = new RetryCommandJava8<>(MAXRETRIES, SECONDSTOWAIT);

        String result = retryCommandJava8.run(() -> {
            if (retryCommand.getRetryCounter() == 0) throw new RuntimeException("Command Failed");
            else return SUCCESS;
        });

        assertEquals(SUCCESS, result);
        assertEquals(1, retryCommand.getRetryCounter());
    }

    public void testRetryCommandShouldThrowExceptionWhenMaxRetriesIsReached() {
        retryCommandJava8 = new RetryCommandJava8<>(MAXRETRIES, SECONDSTOWAIT);

        try {
            retryCommand.run(() -> {throw new RuntimeException("Failed");});
            fail("Should throw exception when max retries is reached");
        } catch (Exception e) { }
    }
}

Of course this example is a stripped down version of what we use, which does waits between retries, back off retries, and proper logging of errors, etc. I just wanted to use a retry command as my example code for trying out function passing in Java 8. However, I hope maybe you will find this useful if you are trying to get a working example going for your first use of function passing in Java 8.

If you are new to Java 8 (just like I am) I recommend reading Everything About Java 8.

Next Page »

The Rubric Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 42 other followers