Permalink
Browse files

Add functionality and tests for the concept of 'no network available'…

… where a previously cached (but stale) entry is returned.
  • Loading branch information...
1 parent 94686b2 commit 18416b87f12559e15df674331acdf3a23549056d @rolfl committed Mar 19, 2012
View
@@ -69,6 +69,7 @@
<path id="unit.test.classpath">
<pathelement location="${classes}" />
+ <pathelement location="src/test" />
<pathelement location="${tests}" />
<pathelement location="${junit.jar}"/>
<pathelement location="lib/jetty/jetty-continuation-${jettyver}.jar" />
@@ -25,6 +25,7 @@
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
+import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@@ -132,6 +133,22 @@ public CacheEntryCorruptException(String message) {
}
+ /**
+ * Used to indicate that there is network problem with a URL.
+ *
+ * @author Rolf Lear
+ *
+ */
+ private static final class NoAccessException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoAccessException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+
/** The lock interrupt thread gets a unique ID based on this. */
private static final AtomicInteger threadid = new AtomicInteger();
@@ -592,6 +609,14 @@ public CachedHTTPRepository(File cachedir) throws IOException {
locktimeout.set(30000); // 30 seconds.
cachefolder = cachedir;
}
+
+ /**
+ * Get the location of the Cache.
+ * @return The Cache folder location.
+ */
+ public File getCacheFolder() {
+ return cachefolder;
+ }
/**
* Get the current user agent used by this Repository
@@ -844,6 +869,8 @@ public Resource resolve(final String publicID, final URL url) throws IOException
checkProperty(props, RESKEY, kk);
checkProperty(props, SYSTEMURL, urlef);
+
+ boolean stale = false;
// all properties are in order
// cache entry is complete...
@@ -872,7 +899,14 @@ public Resource resolve(final String publicID, final URL url) throws IOException
// resource is up to date on the server....
// if the server file has changed, we throw a
// CacheEntryCorrupt exception....
- checkResource(time, lk, channel, lastmod, props, url);
+ try {
+ checkResource(time, lk, channel, lastmod, props, url);
+ } catch (NoAccessException nae) {
+ // we have an existing, expired, but otherwise valid cache
+ // entry. We cannot connect to a network. We return the expired
+ // entry.
+ stale = true;
+ }
}
@@ -887,7 +921,7 @@ public Resource resolve(final String publicID, final URL url) throws IOException
rafxthrow = true;
// return our new resource
- return new Resource(data, null, charset, publicID, urlef, expires);
+ return new Resource(data, null, charset, publicID, urlef, expires, stale);
} catch (CacheEntryCorruptException cece) {
if (lockshared) {
@@ -902,10 +936,18 @@ public Resource resolve(final String publicID, final URL url) throws IOException
}
// we have an exclusive lock, and there's something amiss with the
// cache entry, recreate it.
- final Resource ret = recreateResource(lk, kk, channel, datafile, url, publicID, time);
- // indicate a close() failure should be thrown
- rafxthrow = true;
- return ret;
+ try {
+ final Resource ret = recreateResource(lk, kk, channel, datafile, url, publicID, time);
+ // indicate a close() failure should be thrown
+ rafxthrow = true;
+ return ret;
+ } catch (NoAccessException nae) {
+ final Throwable cause = nae.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException)cause;
+ }
+ throw new IOException("Unable to recreate Resource " + url, cause);
+ }
}
} finally {
@@ -954,7 +996,7 @@ public Resource resolve(final String publicID, final URL url) throws IOException
* @throws IOException for the normal reasons.
*/
private HttpURLConnection getConnection(final String method, final URL url,
- final String lastmod) throws IOException {
+ final String lastmod) throws NoAccessException, IOException {
final HttpURLConnection con = (HttpURLConnection) url.openConnection();
// we are a cache ourselves... we want to punch through to the real source.
con.setUseCaches(false);
@@ -971,7 +1013,14 @@ private HttpURLConnection getConnection(final String method, final URL url,
// set our user-agent.
con.setRequestProperty("User-Agent", useragent.get());
// push through the actual connection.
- con.connect();
+ try {
+ con.connect();
+ } catch (SocketException ce) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE, "Unable to connect to " + url, ce);
+ }
+ throw new NoAccessException("Unable to connect to URL", ce);
+ }
return con;
}
@@ -985,10 +1034,11 @@ private HttpURLConnection getConnection(final String method, final URL url,
* @param url The url of the web resource
* @throws IOException If there is a read problem
* @throws CacheEntryCorruptException If the web resource has been changed.
+ * @throws NoAccessException
*/
private void checkResource(final long time, final String lk,
final FileChannel channel, final String lastmod, final Properties props,
- final URL url) throws IOException, CacheEntryCorruptException {
+ final URL url) throws IOException, CacheEntryCorruptException, NoAccessException {
// we need to check that the resource is up to date.
// we use the 'HEAD' method to get the state
final HttpURLConnection con = getConnection("HEAD", url, lastmod);
@@ -1057,7 +1107,8 @@ private void checkResource(final long time, final String lk,
}
private Resource recreateResource(final String lk, final String key, final FileChannel channel,
- final File datafile, final URL url, final String publicID, final long time) throws IOException {
+ final File datafile, final URL url, final String publicID, final long time)
+ throws IOException, NoAccessException {
// we need to update the resource.
long start = System.currentTimeMillis();
final HttpURLConnection con = getConnection("GET", url, null);
@@ -1164,7 +1215,7 @@ private Resource recreateResource(final String lk, final String key, final FileC
fos.close();
fosok = true;
- return new Resource(baos.toByteArray(), null, enc, publicID, url.toExternalForm(), exp);
+ return new Resource(baos.toByteArray(), null, enc, publicID, url.toExternalForm(), exp, false);
} finally {
if (!fosok) {
@@ -22,6 +22,7 @@
private final String publicID;
private final String systemID;
private final long expires;
+ private final boolean stale;
/**
* Construct a resource.
@@ -31,17 +32,19 @@
* @param publicID The publicID (may be null)
* @param systemID The systemID
* @param expires When this Resource expires.
+ * @param stale indicates whether this Resource is stale.
*/
public Resource(final byte[] bdata, final char[] cdata,
final String encoding, final String publicID,
- final String systemID, final long expires) {
+ final String systemID, final long expires, final boolean stale) {
super();
this.bdata = bdata;
this.cdata = cdata;
this.encoding = encoding;
this.publicID = publicID;
this.systemID = systemID;
this.expires = expires;
+ this.stale = stale;
}
@Override
@@ -70,10 +73,25 @@ public String getSystemId() {
return systemID;
}
- long getExpires() {
+ /**
+ * Get the time at which this entry expires.
+ * @return The expires time.
+ */
+ public long getExpires() {
return expires;
}
+ /**
+ * Indicate whether the cache entry was retuened even though it is stale.
+ * If true it means that we have an expired cache entry, but there is no
+ * way to connect to the origin server to revalidate it.
+ *
+ * @return true if this Resource is stale.
+ */
+ public boolean isStale() {
+ return stale;
+ }
+
@Override
public void setByteStream(InputStream arg0) {
@@ -1,12 +1,17 @@
package net.tuis.resolver;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
+import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -66,4 +71,38 @@ public void testResolveLocal() throws IOException {
assertTrue(xhtmldtd != null);
}
+ private void copyResource(final File destination, final InputStream source) throws IOException {
+ destination.getParentFile().mkdirs();
+ final FileOutputStream fos = new FileOutputStream(destination);
+ final byte[] buffer = new byte[1024];
+ int len = 0;
+ while ((len = source.read(buffer)) >= 0) {
+ fos.write(buffer, 0, len);
+ }
+ fos.flush();
+ fos.close();
+ source.close();
+ }
+
+ @Test
+ public void testResolveNoConnection() throws IOException {
+ final Properties props = new Properties();
+ final String resbase = TestCachedHTTPRepository.class.getName().replace('.', '/');
+ final InputStream is = ClassLoader.getSystemResourceAsStream(resbase + "_control");
+ props.load(is);
+ final String key = props.getProperty("CURL_RESKEY");
+ final String url = props.getProperty("CURL_URL");
+
+ final CachedHTTPRepository repo = new CachedHTTPRepository();
+
+ final File control = new File(repo.getCacheFolder(), key + ".control");
+ final File data = new File(repo.getCacheFolder(), key + ".data");
+
+ copyResource(control, ClassLoader.getSystemResourceAsStream(resbase + "_control"));
+ copyResource(data, ClassLoader.getSystemResourceAsStream(resbase + "_data"));
+
+ final String val = result(repo.resolve(null, new URL(url)));
+ assertEquals("0001332089779999\n", val);
+ }
+
}
@@ -0,0 +1,21 @@
+#CachedHTTPRepository
+#Sun Mar 18 12:56:19 EDT 2012
+Date=Sun, 18 Mar 2012 12\:56\:17 EDT
+Cache-Control=max-age\=2
+CURL_URL=http\://localhost\:4321/TestCachedHTTPRepository
+CURL_EXPIRES=1332089777935
+Content-MD5=mmZ+h1I5Dvku8mh4Rt1RBQ\=\=
+CURL_EXPIRESHR=Sun Mar 18 12\:56\:17 EDT 2012
+Expires=Sun, 18 Mar 2012 12\:56\:19 EDT
+CURL_CHARSET=ISO-8859-1
+Content-Type=text/plain;charset\=ISO-8859-1
+CURL_MODIFIEDHR=Sun Mar 18 08\:56\:15 EDT 2012
+Server=Jetty(8.1.2.v20120308)
+CURL_MD5=mmZ+h1I5Dvku8mh4Rt1RBQ\=\=
+null=HTTP/1.1 200 OK
+CURL_CREATED=Sun Mar 18 12\:56\:19 EDT 2012
+Last-Modified=Sun, 18 Mar 2012 12\:56\:15 EDT
+CURL_RESKEY=http/localhost~4321/TestCachedHTTPRepository
+CURL_SPEED=2.004 seconds
+CURL_MODIFIED=1332075375000
+Content-Length=17
@@ -0,0 +1 @@
+0001332089779999

0 comments on commit 18416b8

Please sign in to comment.