Using the classes CacheRequest and CacheResponse

JDK 1.5 provides a framework into which an caching implementation can be plugged. A standard protocol handler can use the ResponseCache class to get documents from the cache. For example, if the flag useCaches is set on a URLConnection (true by default), an attempt is made to fetch the content from the cache, implementation specified by ResponseCache.

Here’s an example with implementations of ResponseCache, CacheResponse and CacheRequest:

Main.java:

import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.*;

public class Main
{
   public static void main(String []args) throws Exception {
      if (args.length != 1) {
         System.err.println("Usage: java Main <URL>");
         System.exit(0);
      }

      ResponseCache.setDefault(new MyResponseCache());

      // download first time
      System.out.println("Downloading " + args[0] + " a first time");
      String doc = download(args[0]);
      
      System.out.println("nnDownloading " + args[0] + " a second time");
      // download second time (automatically from cache!)
      doc = download(args[0]);
   }
   
   public static String download(String urlString) throws Exception {          
      URL url = new URL(urlString);
      URLConnection connection = url.openConnection();
 
      connection.setConnectTimeout(5000);
      connection.connect();

      BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
      String lineSeparator = System.getProperty("line.separator");
      StringBuffer sb = new StringBuffer();
      String line;
      while ((line = br.readLine()) != null) {
         sb.append(line);
         sb.append(lineSeparator);
      }
      
      return sb.toString();
   }
}

class MyResponseCache extends ResponseCache
{
   public CacheResponse get(URI uri, String requestMethod, Map<String,List<String>> requestHeaders) {
      String cacheFilename = createCacheFilename(uri);
      if (!new File(cacheFilename).exists()) {
         System.out.println(""+uri + " does not exist in cache.");
         return null;
      }
      
      System.out.println("Retrieving " + uri + " from cache.");
      return new MyCacheResponse(createCacheFilename(uri));
   }
   
   public CacheRequest put(URI uri, URLConnection conn) {  
      System.out.println("Storing " + uri + " in cache.");    
      return new MyCacheRequest(createCacheFilename(uri));      
   }
   
   private String createCacheFilename(URI uri) {
      String suri = uri.toString();
      
      String notAllowed = "\/:*?"<>|";
      StringBuffer result = new StringBuffer();
      for (int i=0; i<suri.toString().length(); i++) {
         if (notAllowed.indexOf(suri.charAt(i)) == -1) {
            result.append(suri.charAt(i));
         }
      }
      return result.toString();   
   }
}

class MyCacheResponse extends CacheResponse
{
   private String filename;

   public MyCacheResponse(String filename) {
      this.filename = filename;
   }

   public Map<String, List<String>> getHeaders() throws IOException {
      return new HashMap<String, List<String>>();
   }

   public ReadableByteChannel getBody() throws IOException {
      return Channels.newChannel(new BufferedInputStream(new FileInputStream(filename)));
   }
}

class MyCacheRequest extends CacheRequest
{
   private String filename;
     
   public MyCacheRequest(String filename) {
      this.filename = filename;
   }
   
   public WritableByteChannel getBody() throws IOException {
      return Channels.newChannel(new BufferedOutputStream(new FileOutputStream(filename)));
   }
   
   public void abort() {
   }
}

Get started with an ArrayBlockingQueue

An ArrayBlockingQueue is a FIFO blocking queue with an array used by the underlying implementation. At construction, you specify the maximum space used by the queue. Attempting to put more elements in the queue than is allowed by its capacity constraint will result in a blocking wait. Similarly, attempting to take an element from an empty queue will block.

The following example starts up two threads, a consumer and a producer, that communicate with each other with an ArrayBlockingQueue and generic Message objects. The capacity is 5 elements. The producer tries to put 10 elements on the queue, but blocks until space in the queue is freed up by the consumer.

Main.java:

import java.util.concurrent.*;
 
public class Main
{
   public static void main(String []args) {
      BlockingQueue blockingQueue = new ArrayBlockingQueue<Message>(5);
       
      Producer p = new Producer(blockingQueue);
      new Thread(p).start();
 
      // wait a bit before consuming to allow the queue to fill up
      // and force a blocking wait
      try { Thread.sleep(1000); } catch(InterruptedException e) { }
      Consumer c = new Consumer(blockingQueue);
      new Thread(c).start();
   }
}
 
class Producer implements Runnable
{
   private BlockingQueue<Message> blockingQueue;
 
   public Producer(BlockingQueue<Message> blockingQueue) {
      this.blockingQueue = blockingQueue;
   }
 
   public void run() {
      for (int i=0; i<10; i++) {
         Message<String> m = new Message<String>("message contents #" + i);
         System.out.println("Producing '" + m + "'");
         try {
            blockingQueue.put(m);
         }
         catch(InterruptedException e) {
            return;
         }  
      }
   }
}
 
class Consumer implements Runnable
{
   private BlockingQueue<Message> blockingQueue;
   
   public Consumer(BlockingQueue<Message> blockingQueue) {
      this.blockingQueue = blockingQueue;
   }
    
   public void run() {
    
      while (true) {
         try {
            Message m = blockingQueue.take();
            System.out.println("tConsuming '" + m + "'");
         }
         catch(InterruptedException e) { } 
      }
   }
}
 
class Message<T> {
   private T contents;
    
   public Message(T contents) {
      this.contents = contents;
   }
    
   public T getContents() {
      return contents;
   }
    
   public String toString() {
      return ""+contents;
   }
}

outputs:

Producing 'message contents #0'
Producing 'message contents #1'
Producing 'message contents #2'
Producing 'message contents #3'
Producing 'message contents #4'
Producing 'message contents #5'
Producing 'message contents #6'
        Consuming 'message contents #0'
Producing 'message contents #7'
        Consuming 'message contents #1'
Producing 'message contents #8'
        Consuming 'message contents #2'
Producing 'message contents #9'
        Consuming 'message contents #3'
        Consuming 'message contents #4'
        Consuming 'message contents #5'
        Consuming 'message contents #6'
        Consuming 'message contents #7'
        Consuming 'message contents #8'
        Consuming 'message contents #9'

Notice that the following warnings are generated when compiling the above code:

Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

The reason for this is that the ArrayBlockingQueue is cast to a BlockingQueue without a specified type parameter (Message). This means that type safety may be broken. In fact, we could simply insert the following line of code to produce a ClassCastException at runtime:

      BlockingQueue blockingQueue = new ArrayBlockingQueue<Message>(5);
 
      try { blockingQueue.put("hello"); } catch(InterruptedException e) { }

Solve this by specifying the type parameter:

      BlockingQueue<Message> blockingQueue = new ArrayBlockingQueue<Message>(5);

An example of the other methods of the ArrayBlockingQueue can be found here.