forked from eclipse-archived/californium.core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dealt with issue eclipse-archived#5 (ETag convenience). Added an inte…
…rface ETagSupport and a default implementation ETagDefaultSupport and JUnit tests. A CoapResource now has a ETagSupport object that generates ETags if required. If an incoming request has ETags, the resource automatically validates them against the current value in the ETagSupport object and responds with a 2.03 (Valid) if an ETag matches.
- Loading branch information
1 parent
da7110d
commit 7c334f8
Showing
5 changed files
with
372 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
...-core/src/main/java/org/eclipse/californium/core/server/resources/ETagDefaultSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package org.eclipse.californium.core.server.resources; | ||
|
||
import java.util.Random; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
/** | ||
* This is e default ETag support implementation for a resource. An instance of | ||
* this class holds the current ETag. If the content of a resource changes, it | ||
* should call {@link #nextETag()} to increase the ETag value by 1. This class | ||
* is thread-safe. | ||
*/ | ||
public class ETagDefaultSupport implements ETagSupport { | ||
|
||
/** The maximum size allowed by the coap-18 draft. */ | ||
public static final int MAX_BYTES = 8; | ||
|
||
/** The minimum size allowed by the coap-18 draft. */ | ||
public static final int MIN_BYTES = 0; | ||
|
||
/** A static instance of random to set random initial ETag values. */ | ||
private static final Random rand = new Random(); | ||
|
||
/** The size of the ETag. */ | ||
private int bytes; | ||
|
||
/** The current ETag value. */ | ||
private AtomicLong current; | ||
|
||
/** | ||
* Instantiates a new instance of ETagDefaultSupport that generates 8 bytes | ||
* and starts with a random value. long ETags. | ||
*/ | ||
public ETagDefaultSupport() { | ||
this(MAX_BYTES); | ||
} | ||
|
||
/** | ||
* Instantiates a new instance of ETagDefaultSupport that generates ETags of | ||
* the specified size and starts with a random value. | ||
* | ||
* @param bytes the size of the ETags | ||
*/ | ||
public ETagDefaultSupport(int bytes) { | ||
this(bytes, rand.nextLong()); | ||
} | ||
|
||
/** | ||
* Instantiates a new instance of ETagDefaultSupport that generates ETags of | ||
* the specified size and starts with the specified value. | ||
* | ||
* @param bytes the size of the ETags | ||
* @param initialValue the initial value | ||
*/ | ||
public ETagDefaultSupport(int bytes, long initialValue) { | ||
current = new AtomicLong(initialValue); | ||
if (bytes > MAX_BYTES) | ||
throw new IllegalArgumentException("ETags must not be longer than 8 bytes but is "+bytes); | ||
if (bytes < MIN_BYTES) | ||
throw new IllegalArgumentException("ETags must be at least 0 bytes long but is "+bytes); | ||
this.bytes = bytes; | ||
} | ||
|
||
/* (non-Javadoc) | ||
* @see org.eclipse.californium.core.server.resources.ETagSupport#nextETag() | ||
*/ | ||
public byte[] nextETag() { | ||
return long2bytes(current.incrementAndGet(), bytes); | ||
} | ||
|
||
/* (non-Javadoc) | ||
* @see org.eclipse.californium.core.server.resources.ETagSupport#getCurrentETag() | ||
*/ | ||
public byte[] getCurrentETag() { | ||
return long2bytes(current.get(), bytes); | ||
} | ||
|
||
/* (non-Javadoc) | ||
* @see org.eclipse.californium.core.server.resources.ETagSupport#setCurrentETag(byte[]) | ||
*/ | ||
public void setCurrentETag(byte[] etag) { | ||
long cur = 0; | ||
for (int i=0;i<etag.length && i<bytes;i++) | ||
cur |= (etag[i] << (etag.length - i - 1)*8); | ||
this.current.set(cur); | ||
} | ||
|
||
/* (non-Javadoc) | ||
* @see org.eclipse.californium.core.server.resources.ETagSupport#getCurrentETagAsString() | ||
*/ | ||
public String getCurrentETagAsString() { | ||
byte[] etag = getCurrentETag(); | ||
StringBuffer string = new StringBuffer(""); | ||
for(byte b:etag) string.append(String.format("%02x", b&0xff)); | ||
return string.toString(); | ||
} | ||
|
||
/** | ||
* Converts the specified long value to a byte array of maximum maxSize | ||
* length. Cuts off leading zeros. | ||
* | ||
* @param value the value | ||
* @param maxSize the max size of the byte array | ||
* @return the byte[] the byte array | ||
*/ | ||
private byte[] long2bytes(long value, int maxSize) { | ||
int length = maxSize; | ||
// reduce length so that there will be no leading zeros | ||
for (;( (value >>> (length-1)*8) & 0xFF) == 0 && length > 0;length--); | ||
byte[] etag = new byte[length]; | ||
for (int i=0;i<length;i++) { | ||
etag[length - i - 1] = (byte) (value >>> i*8); | ||
} | ||
return etag; | ||
} | ||
|
||
/* (non-Javadoc) | ||
* @see java.lang.Object#toString() | ||
*/ | ||
public String toString() { | ||
return "DefaultETagSupport(current: 0x"+getCurrentETagAsString()+", bytes: "+bytes+")"; | ||
} | ||
|
||
} |
37 changes: 37 additions & 0 deletions
37
...fornium-core/src/main/java/org/eclipse/californium/core/server/resources/ETagSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package org.eclipse.californium.core.server.resources; | ||
|
||
/** | ||
* An ETag Support object supports a resource in generating and maintaining | ||
* ETags. | ||
*/ | ||
public interface ETagSupport { | ||
|
||
/** | ||
* Generates the next ETag. | ||
* | ||
* @return the ETag as byte array | ||
*/ | ||
public byte[] nextETag(); | ||
|
||
/** | ||
* Gets the current ETag. | ||
* | ||
* @return the current ETag | ||
*/ | ||
public byte[] getCurrentETag(); | ||
|
||
/** | ||
* Sets the current ETag. | ||
* | ||
* @param etag the new ETag | ||
*/ | ||
public void setCurrentETag(byte[] etag); | ||
|
||
/** | ||
* Gets the current ETag as string. | ||
* | ||
* @return the current ETag as string | ||
*/ | ||
public String getCurrentETagAsString(); | ||
|
||
} |
118 changes: 118 additions & 0 deletions
118
...fornium-core/src/test/java/org/eclipse/californium/core/test/ETagSupportResourceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package org.eclipse.californium.core.test; | ||
|
||
import java.util.Arrays; | ||
|
||
import org.eclipse.californium.core.CoapClient; | ||
import org.eclipse.californium.core.CoapResponse; | ||
import org.eclipse.californium.core.CoapServer; | ||
import org.eclipse.californium.core.coap.CoAP.ResponseCode; | ||
import org.eclipse.californium.core.network.CoAPEndpoint; | ||
import org.eclipse.californium.core.server.resources.CoapExchange; | ||
import org.eclipse.californium.core.server.resources.ConcurrentCoapResource; | ||
import org.junit.After; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
public class ETagSupportResourceTest { | ||
|
||
public static final String TARGET = "test"; | ||
|
||
private CoapServer server; | ||
private int serverPort; | ||
|
||
private TestResource testResource; | ||
|
||
@Before | ||
public void startupServer() throws Exception { | ||
System.out.println("\nStart "+getClass().getSimpleName()); | ||
|
||
CoAPEndpoint serverEndpoint = new CoAPEndpoint(); | ||
server = new CoapServer(); | ||
server.addEndpoint(serverEndpoint); | ||
server.add(testResource = new TestResource(TARGET)); | ||
server.start(); | ||
|
||
serverPort = serverEndpoint.getAddress().getPort(); | ||
} | ||
|
||
@After | ||
public void shutdownServer() { | ||
server.destroy(); | ||
System.out.println("End "+getClass().getSimpleName()); | ||
} | ||
|
||
@Test | ||
public void test() { | ||
String currentContent = "AAAA"; | ||
testResource.setContent(currentContent); | ||
|
||
// First GET request expects a 2.05 response with an ETag. | ||
CoapClient client = new CoapClient("coap://localhost:"+serverPort+"/"+TARGET); | ||
CoapResponse res1 = client.get(); | ||
Assert.assertNotNull(res1); | ||
Assert.assertEquals(ResponseCode.CONTENT, res1.getCode()); | ||
Assert.assertEquals(currentContent, res1.getResponseText()); | ||
Assert.assertTrue(res1.getOptions().getETagCount() > 0); | ||
|
||
// Second request validates that the ETag from the first response is still valid. | ||
byte[] etag1 = res1.getOptions().getETags().get(0); | ||
CoapResponse res2 = client.validate(etag1); | ||
Assert.assertNotNull(res2); | ||
Assert.assertEquals(ResponseCode.VALID, res2.getCode()); | ||
Assert.assertTrue(res2.getResponseText().isEmpty()); | ||
Assert.assertTrue(res2.getOptions().getETagCount() > 0); | ||
Assert.assertArrayEquals(etag1, res2.getOptions().getETags().get(0)); | ||
|
||
// The content changes | ||
currentContent = "BBBB"; | ||
testResource.setContent(currentContent); | ||
|
||
// The third request attempts to validate that the ETag from the first response is still valid. | ||
// However, since the content has changed, the server responds a 2.05 with the new content and a new ETag. | ||
CoapResponse res3 = client.validate(etag1); | ||
Assert.assertNotNull(res3); | ||
Assert.assertEquals(ResponseCode.CONTENT, res3.getCode()); | ||
Assert.assertEquals(currentContent, res3.getResponseText()); | ||
Assert.assertTrue(res3.getOptions().getETagCount() > 0); | ||
Assert.assertFalse(Arrays.equals(etag1, res3.getOptions().getETags().get(0))); // has another ETag | ||
|
||
// The forth request validates the ETags from the first and third response. | ||
// The server responds that the third ETag is still valid. | ||
byte[] etag3 = res3.getOptions().getETags().get(0); | ||
CoapResponse res4 = client.validate(etag1, etag3); | ||
Assert.assertNotNull(res4); | ||
Assert.assertEquals(ResponseCode.VALID, res4.getCode()); | ||
Assert.assertTrue(res4.getResponseText().isEmpty()); | ||
Assert.assertTrue(res4.getOptions().getETagCount() > 0); | ||
Assert.assertArrayEquals(etag3, res4.getOptions().getETags().get(0)); | ||
|
||
} | ||
|
||
private static class TestResource extends ConcurrentCoapResource { | ||
|
||
private String content; | ||
|
||
public TestResource(String name) { | ||
super(name, ConcurrentCoapResource.SINGLE_THREADED); | ||
} | ||
|
||
@Override | ||
public void handleGET(CoapExchange exchange) { | ||
exchange.setETag(getETagSupport().getCurrentETag()); | ||
exchange.respond(content); | ||
} | ||
|
||
public void setContent(final String newContent) { | ||
// This Changes the content and update the ETag. This is executed by | ||
// the same thread that also handles requests so that there are no | ||
// race conditions! | ||
execute(new Runnable() { | ||
public void run() { | ||
content = newContent; | ||
getETagSupport().nextETag(); | ||
} | ||
}); | ||
} | ||
} | ||
} |
Oops, something went wrong.