Dynamic DNS using AWS Route 53 and AWS Java SDK

Route 53 is the Amazon Web Services (AWS) DNS service. Assuming your domain’s DNS is hosted with Route 53, you can create a utility in Java, using the AWS Java SDK, to update a hostname under your domain that points to a dynamic IP address. This may be useful if for example your home’s public IP address changes often, and you want to be able to access it remotely.

To start off, you’ll need to create a hostname in AWS Route 53 that maps to an “A” record pointing to an IP address (doesn’t matter what IP address at this point, since we’ll update it through code later). This can be done manually online, and should be pretty self-explanatory once you open up the Route 53 control panel in the AWS web console.

Let’s say your domain name is domain.com. And you want to dynamically update two hosts: home.domain.com, and dynamic.domain.com, to point to the IP address of a machine that has a dynamically assigned IP.

For this, you can use the following code snippit which I whipped up using the AWS Java SDK documentation for Route 53, and with lots of trial and error:

package utils;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Logger;

import org.xbill.DNS.ARecord;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Type;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.route53.AmazonRoute53;
import com.amazonaws.services.route53.AmazonRoute53ClientBuilder;
import com.amazonaws.services.route53.model.Change;
import com.amazonaws.services.route53.model.ChangeAction;
import com.amazonaws.services.route53.model.ChangeBatch;
import com.amazonaws.services.route53.model.ChangeResourceRecordSetsRequest;
import com.amazonaws.services.route53.model.GetHostedZoneRequest;
import com.amazonaws.services.route53.model.HostedZone;
import com.amazonaws.services.route53.model.ListResourceRecordSetsRequest;
import com.amazonaws.services.route53.model.ListResourceRecordSetsResult;
import com.amazonaws.services.route53.model.ResourceRecord;
import com.amazonaws.services.route53.model.ResourceRecordSet;

public class DynamicDNSUpdater {
	static String AWS_ACCESS_KEY_ID = "xxx";
	static String AWS_SECRET_KEY_ID = "xxx";
	static String ROUT53_HOSTED_ZONE_ID = "Zxxxxxxxxxxxxx";
	static String[] HOSTNAMES_TO_UPDATE = { "home.domain.com", "dynamic.domain.com" };

	static void UpdateIP() throws Exception
	{
		Logger log = ...;

		HashSet<String> hostnamesNeedingUpdate = new HashSet<String>();

		URL awsCheckIpURL = new URL("http://checkip.amazonaws.com");
		HttpURLConnection awsCheckIphttpUrlConnection = (HttpURLConnection) awsCheckIpURL.openConnection();
		BufferedReader awsCheckIpReader = new BufferedReader(new InputStreamReader(awsCheckIphttpUrlConnection.getInputStream()));
		String thisMachinePublicIp = awsCheckIpReader.readLine();
		log.fine("Current public IP of this machine: "+thisMachinePublicIp);
		
	    Resolver resolver = new SimpleResolver("8.8.8.8");
		for(String hostname : HOSTNAMES_TO_UPDATE)
		{
		    Lookup lookup = new Lookup(hostname, Type.A);
		    lookup.setResolver(resolver);
		    Record[] records = lookup.run();
		    String address = ((ARecord) records[0]).getAddress().toString();
		    address = address.substring(address.lastIndexOf("/")+1);
			if(!address.equals(thisMachinePublicIp))
			{
				log.fine("!!! Needs update: "+hostname+". Current IP: "+address+". New public IP: "+thisMachinePublicIp);
				hostnamesNeedingUpdate.add(hostname+".");
			}
		}

		if(hostnamesNeedingUpdate.size()>0)
		{
			BasicAWSCredentials awsCreds = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY_ID);
			AmazonRoute53 route53 = AmazonRoute53ClientBuilder
					.standard()
					.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
					.withRegion(Constants.AWS_REGIONS)
					.build(); 
		    HostedZone hostedZone = route53.getHostedZone(new GetHostedZoneRequest(ROUT53_HOSTED_ZONE_ID)).getHostedZone();

		    ListResourceRecordSetsRequest listResourceRecordSetsRequest = new ListResourceRecordSetsRequest()
		            .withHostedZoneId(hostedZone.getId());
		    ListResourceRecordSetsResult listResourceRecordSetsResult = route53.listResourceRecordSets(listResourceRecordSetsRequest);
		    List<ResourceRecordSet>	resourceRecordSetList = listResourceRecordSetsResult.getResourceRecordSets();
	    	List<Change> changes = new ArrayList<Change>();
		    for(ResourceRecordSet resourceRecordSet : resourceRecordSetList)
		    {
		    	if(resourceRecordSet.getType().equals("A") && hostnamesNeedingUpdate.contains(resourceRecordSet.getName()))
		    	{
			    	List<ResourceRecord> resourceRecords = new ArrayList<ResourceRecord>();
			    	ResourceRecord resourceRecord = new ResourceRecord();
			    	resourceRecord.setValue(thisMachinePublicIp);
			    	resourceRecords.add(resourceRecord);
			    	resourceRecordSet.setResourceRecords(resourceRecords);
			    	Change change = new Change(ChangeAction.UPSERT, resourceRecordSet);
			    	changes.add(change);
			    	log.fine("Updating "+resourceRecordSet.getName()+" to A "+thisMachinePublicIp);
		    	}
		    }
		    if(changes.size()>0)
		    {
		    	ChangeBatch changeBatch = new ChangeBatch(changes);
		    	ChangeResourceRecordSetsRequest changeResourceRecordSetsRequest = new ChangeResourceRecordSetsRequest()
		    			.withHostedZoneId(ROUT53_HOSTED_ZONE_ID)
		    			.withChangeBatch(changeBatch);
		    	route53.changeResourceRecordSets(changeResourceRecordSetsRequest);
		    	log.fine("Done!");
		    }
		    else
		    {
		    	log.fine("None of the specified hostnames found in this zone");
		    }
		}
		else
			log.fine("No updates required!");
	}

	public static void main(String args[]) throws Exception {
		UpdateIP();
	}
}

In order for this to work correctly, you’ll need to set up an AWS API key. This key will need either full access to your AWS account, or at least access to Route53. The documentation for setting it up is available at AWS.

You’ll need to update the AWS_ACCESS_KEY_ID and AWS_SECRET_KEY_ID in the code block above with the key details you get from AWS. And then you’ll need to update ROUT53_HOSTED_ZONE_ID with the Zone ID of your domain hosted in Route 53 (it begins with Z, at least as far as I’ve noticed). And, of course, you’ll need to update HOSTNAMES_TO_UPDATE with the hostname(s) that need to be dynamically updated with the public IP of the machine running this utility.

Here’s a quick breakdown of the code: We start by getting the public IP of the machine this code is running on, and then we look up the IP of the hostnames provided. If these don’t match, that means an update with the new IP is needed. That’s when the com.amazonaws.services.route53.AmazonRoute53 class is used to do the following: using the AWS API access key, it gets a list of all the “A” records for the hosted zone provided. It then loops through the hostnames needing update, and simply posts a com.amazonaws.services.route53.AmazonRoute53.changeResourceRecordSets() with the new public IP of the machine.

And that’s it! There you have it–a Java util that will dynamically update the IP address for the machine it’s running on.

Now in order to run this utility periodically (so it can actually do what it’s meant to, without you manually running it), you can compile the Java code and stick it in a jar, or a simply just copy the .class files in a directory somewhere. (Note: if you’re using Eclipse, it makes it easy to export your project as an executable jar).

Then, if you’re in Linux, you can set up a crontab entry to run every 5 minutes or so and simply run this java utility from the command line.
Granted Java is installed and available in the system path, the command would look something like: java -cp /path/to/MyUtils.jar utils.DynamicDNSUpdater. And if you’re in windows, you can set up a task with the Windows Task Scheduler to run every 5 minutes and run the same command. Pro tip: if using windows, you may want to use “javaw” instead of “java”, if you don’t want a little window to pop up and disappear periodically when you’re in the middle of on the same machine.

Redirecting all stdout and stderr to Logger in Java

This would seem obvious, but it wasn’t to me, so I thought I’d write about it to help out anyone else attempting to accomplish the same. It’s pretty straight forward actually.

Let’s say you have a java.util.logging.Logger object that you’ve initialized, and you want to redirect all stderr (exceptions, System.err.print()’s) and stdout (System.out.print()) to it. You’ll want to use System.setErr and System.setOut to a custom java.io.PrintStream object which writes to your Logger object.

Let’s first define a class to do this for us, and then I’ll explain how it works:

class CustomOutputStream extends OutputStream 
{
	Logger logger;
	Level level;
	StringBuilder stringBuilder;
	
	public CustomOutputStream(Logger logger, Level level)
	{
		this.logger = logger;
		this.level = level;
		stringBuilder = new StringBuilder();
	}
	
	@Override
	public final void write(int i) throws IOException 
	{
		char c = (char) i;
		if(c == '\r' || c == '\n')
		{
			if(stringBuilder.length()>0)
			{
				logger.log(level,stringBuilder.toString());
				stringBuilder = new StringBuilder();
			}
		}
		else
			stringBuilder.append(c);
	}
}

The way this works is by extending OutputStream and overriding the write() method. But write() only takes one character at a time, so essentially you want to buffer each character into a String Builder, to build up the whole line, until you encounter a \r or \n (carriage return, new line), and then submit it to the logger.

To attach CustomOutputStream to your logger:

Logger logger = Logger.getLogger(...);
//...
System.setErr(
		new PrintStream(
			new CustomOutputStream(logger,Level.SEVERE) //Or whatever logger level you want
			)
		);  
System.setOut(
		new PrintStream(
				new CustomOutputStream(logger,Level.FINE) //Or whatever logger level you
			)
		);

Note: if you’ve configured your logger to always include the class/method with the log message, a side effect of this is that the output will not include your original method that wrote the log message to stderr or stdout , but instead your.package.CustomOutputStream.write().

Happy logging!

Using a SOCKS proxy in Java’s HttpURLConnection

Doing a Google Search on how to get Java’s URLConnection or HttpURLConnection to use a SOCKS proxy yields many results on how to pass in arguments to the JVM to set it up, or to call System.setProperty(), which then sets the SOCKS proxy to be used for all HTTP connections through Java. But what if you want to limit it to only certain connections started from HttpURLConnection, or if the proxy address isn’t available until later on?

Here’s a code snippet on how you’d go about doing that programatically.

String proxyString = "127.0.0.1:12345"; //ip:port
String proxyAddress[] = proxyString.split(":");
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(proxyAddress[0], Integer.parseInt(proxyAddress[1])));
URL url = new URL("http://some.website");
HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
//do whatever with httpUrlConnection, it will be connected through the SOCKS proxy!

And that’s it.

Creating your own thread dispatcher in Java

Java offers ExecutorService, which can be used for managing and dispatching threads. But ExecutorService has limitations. One of them being the fact that if you create a fixed thread pool, you need to define all your threads up front. What if you’ll be spawning a million threads (but only a handful will be running at a given moment)? That would take up a lot of memory. And in some cases, you may need to know the outcome of certain threads to schedule new threads. It’s hard and cumbersome to do this using ExecutorService.

On the other hand, you can easily create your own thread dispatcher service, which can be limited to run only a certain number of threads at a time. See code snippet below:

final static int MAX_THREADS_AT_A_TIME = 10;
static int currentlyRunningThreadsCount = 0;
static Object dispatcherLock = new Object();

public static void main(String args[])
{
	for(int i=0; i<100; i++)
	{
		synchronized(dispatcherLock)
		{
			currentlyRunningThreadsCount++;
		}
		final int thisThreadCount = i+1;
		new Thread(new Runnable() {
			@Override
			public void run() {
				//Do something
				System.out.println("Thread "+thisThreadCount+" starting.");
				try { Thread.sleep(5000); } catch(InterruptedException e) { }
				System.out.println("Thread "+thisThreadCount+" finished.");
				synchronized(dispatcherLock)
				{
					currentlyRunningThreadsCount--;
					dispatcherLock.notify();
				}
			}
		}).start();
		if(currentlyRunningThreadsCount >= MAX_THREADS_AT_A_TIME)
		{
			synchronized(dispatcherLock)
			{
				try { dispatcherLock.wait(); } catch(InterruptedException e) { }
			}
		}
	}
}

Let’s break it down. In this case we’re spawning 100 threads, but limiting it to run only 10 threads at a time. Everything outside of the thread runnable is part of the “dispatcher” service. The dispatcher loop uses a counter currentlyRunningThreadsCount to track how many threads are running at a time. If there are 10, it wait()’s on a lock object. And as each thread finishes its work, it decrements currentlyRunningThreadsCount and calls notify() on the lock object, which wakes up the dispatcher and it moves on to spawn more.

Pretty simple, right?!

JasperReports nuances

JasperReports is an engine that can allow you to generate reports in HTML, PDF, or many other formats. When I say reports here, I mean reports that are essentially pieces of paper that convey something meant to be reviewed by someone. Invoices, Statements, Forecasts, or anything that needs to be dynamically presented on paper for review is a good candidate.

One positive about JasperReports is that it’s all Java based, and plugs in well into your Java ecosystem. On the other hand, if you’re just now looking for a reporting engine, I may try to dissuade you from using JasperReports. It seems to have become outdated. There have been few updates in the last couple years (if any), and community support seems to be waning (you only find old blog posts, forums threads, etc). And documentation is also a bit lacking. But if you’re already using JasperReports, this post is for you.

It took me a while to figure how to get JasperReports to do certain things, because again the documentation is weak. So I figured I’d share these insights just in case someone else is struggling with the same thing.

  • Setting the foreground/background color of a field programatically
    This was not obvious at all. Sometimes you need to set the background color or text color of a field dynamically, based on some data value. Let’s say a data field literally has the color in it: for example $F{BACKCOLOR}, and you want to set the background color of a field to the value contained in $F{BACKCOLOR} (say “#0000FF”). In order to accomplish this, you’ll need to edit the properties of the TextField, and set this property:

    net.sf.jasperreports.style.backcolor

    …to this value:

    $F{BACKCOLOR}

And similarly to set the foreground color (text color), set this property for the TextField:

net.sf.jasperreports.style.forecolor

…to a field of your choosing that has the color in it. (Or hard code the color by typing into the value for this property, surrounded by double quotes to specify a constant).

  • JSON queries against your DataSet
    JasperReports seems to play pretty well with JSON. But something that isn’t obvious is a way to query/filter the JSON data within the JasperReports engine, which it is populated into a data set.To demonstrate how to do this, let’s take an example JSON data:

[{“letters”:[{“category”:”A to C”,”data”:[“a”,”b”,”c”]},{“category”:”D to F”,”data”:[“d”,”e”,”f”]}]}]

Or more visually friendly, like so:

JasperReportsJsonExample

Now let’s say you want to limit your JasperReports Data Set to “letters”, and furthermore a certain category. What you need to do is edit the query for the Data Set, and specify the following:

letters(category==A to C)

And that’s it!

  • More to come later

AmazonS3Client to loop through batches of S3 files objects

AWS provides the AmazonS3Client class, which is part of the AWS Java SDK. This class can be used to interact with files in S3.

An important feature to note of the AmazonS3Client is that it limits results to batches of 1000. If you have less than 1000 files, then all is good. You can use amazonS3Client.listObjects(bucketName); and it will provide all the objects in a bucket.

But if the bucket contains more than 1000 files, you will need to loop through the files in batches. This is not entirely obvious and can cause you to miss files (as I certainly did)!

To get started, you would initiate AmazonS3Client like so:

AmazonS3Client amazonS3Client = new AmazonS3Client(new BasicAWSCredentials(KEY, SECRET));

The approach I like to take is to first loop through and collect all the files up front like so:

ObjectListing objectListing = amazonS3Client.listObjects(bucketName);
List<S3ObjectSummary> s3ObjectSummaries = objectListing.getObjectSummaries();
while (objectListing.isTruncated()) 
{
   objectListing = amazonS3Client.listNextBatchOfObjects (objectListing);
   s3ObjectSummaries.addAll (objectListing.getObjectSummaries());
}

Note: if memory is a concern or you have an unlimited number of files, you can simply modify the approach to do whatever you need to with each file as you fetch it in batches from the API, instead of collecting them up front.

If you first collected them in a List up front, you can then loop through each file like so:

for(S3ObjectSummary s3ObjectSummary : s3ObjectSummaries)
{
	String s3ObjectKey = s3ObjectSummary.getKey();
	//Do whatever with s3ObjectSummary

 

Sorting a JSON Array in Java

There are a number of approaches you can take. But a simple one is to first convert it to a Java Collection, and use Collections.sort() with a custom comparator.

The example I’ll follow consists of an org.json.JSONArray which is has (and only has) org.json.JSONObject’s. So a json array of json objects, which is pretty common. Say you want to sort the JSONObjects in the JSONArray, based on a key in the JSONObject.

Let’s start by converting a JSONArray to a Collection of JSONObjects, using the java List type:

List<JSONObject> myJsonArrayAsList = new ArrayList<JSONObject>();
for (int i = 0; i < myJsonArray.length(); i++)
    myJsonArrayAsList.add(myJsonArray.getJSONObject(i));

Now you can use Collections.sort() with a custom comparator. Let’s say you have a key named “key” in each json object, which maps to an int, and you want to sort on int value. You would use the following code:

Collections.sort(myJsonArrayAsList, new Comparator<JSONObject>() {
    @Override
    public int compare(JSONObject jsonObjectA, JSONObject jsonObjectB) {
    	int compare = 0;
    	try
    	{
    		int keyA = jsonObjectA.getInt("key");
    		int keyB = jsonObjectB.getInt("key");
    		compare = Integer.compare(keyA, keyB);
    	}
    	catch(JSONException e)
    	{
    		e.printStackTrace();
    	}
    	return compare;
    }
});

That’ll do. Now, let’s take it a step further. Let’s say the values in the key field of each json object are Strings, and you want to sort based on the Strings, but in a particular order. You want the string “oranges” to come first, “bananas” to come second, “pineapples” to come third, and “apples” to come last.

An easy way to go about this is to create a HashMap, and assign these strings integer values. Then use those integer values to compare. Here’s what the code would look like for that:

Collections.sort(myJsonArrayAsList, new Comparator<JSONObject>() {
    @Override
    public int compare(JSONObject jsonObjectA, JSONObject jsonObjectB) {
    	int compare = 0;
    	try
    	{
			HashMap<String,Integer> fruitTypeSorts = new HashMap<String,Integer>();
			fruitTypeSorts.put("orange", 1);
			fruitTypeSorts.put("bananas", 2);
			fruitTypeSorts.put("pineapples", 3);
			fruitTypeSorts.put("apples", 4);
			int valueA=fruitTypeSorts.get(jsonObjectA.getString("key"));
			int valueB=fruitTypeSorts.get(jsonObjectB.getString("key"));
			return Integer.compare(valueA, valueB);
    	}
    	catch(JSONException e)
    	{
    		e.printStackTrace();
    	}
    	return compare;
    }
});

And voila! Now you have the List sorted based on the key value, with objects with key values oranges being first, then bananas, and so on.

To tie it all together, you want to convert it back to a JSONArray. To put the sorted JSONObjects back into your original array, simply:

myJsonArray = new JSONArray();
for (int i = 0; i < myJsonArrayAsList.size(); i++) {
	myJsonArray.put(myJsonArrayAsList.get(i));
}