diff --git a/README.md b/README.md index a0114e9..58c2ee9 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ Crud HTTP This project provides an implementation of the [Crud API](https://github.com/rickbw/crud-api) for HTTP, based on [Jersey](https://jersey.java.net). The four primary HTTP methods are all supported: -* `GET`: [JerseyReadableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyReadableResource.java), a [ReadableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/ReadableResource.java) -* `PUT`: [JerseyWritableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyWritableResource.java), a [WritableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/WritableResource.java) -* `POST`: [JerseyUpdatableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyUpdatableResource.java), an [UpdatableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/UpdatableResource.java) -* `DELETE`: [JerseyDeletableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyDeletableResource.java), a [DeletableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/DeletableResource.java) +* `GET`: [JerseyReadableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyReadableResource.java), a [ReadableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/spi/ReadableResource.java) +* `PUT`: [JerseyWritableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyWritableResource.java), a [WritableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/spi/WritableResource.java) +* `POST`: [JerseyUpdatableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyUpdatableResource.java), an [UpdatableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/spi/UpdatableResource.java) +* `DELETE`: [JerseyDeletableResource](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyDeletableResource.java), a [DeletableResource](https://github.com/rickbw/crud-api/blob/master/src/main/java/rickbw/crud/spi/DeletableResource.java) Most applications will not use these `Resource` implementation classes directly. Instead, they will start with the corresponding `ResourceProviders`, which implement URI-based lookup of particular `Resources`. For example, [JerseyReadableResourceProvider](https://github.com/rickbw/crud-http/blob/master/src/main/java/rickbw/crud/http/JerseyReadableResourceProvider.java) provides instances of `JerseyReadableResource` on demand. diff --git a/pom.xml b/pom.xml index f433ea2..ec10362 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ the License. rickbw crud-http - 0.5-SNAPSHOT + 0.6-SNAPSHOT jar Crud HTTP Implementation @@ -65,7 +65,7 @@ the License. 1.18 - 0.5-SNAPSHOT + 0.6-SNAPSHOT 4.11 1.8.5 diff --git a/src/main/java/crud/http/ClientRequest.java b/src/main/java/crud/http/ClientRequest.java index 566de2b..e1fef3f 100644 --- a/src/main/java/crud/http/ClientRequest.java +++ b/src/main/java/crud/http/ClientRequest.java @@ -32,12 +32,12 @@ import com.google.common.collect.Sets; import com.sun.jersey.api.client.PartialRequestBuilder; -import crud.ResourceProvider; +import crud.spi.ResourceSet; /** * A container for the state used to initialize HTTP - * {@link ResourceProvider}s. + * {@link ResourceSet}s. */ public class ClientRequest { diff --git a/src/main/java/crud/http/HttpResource.java b/src/main/java/crud/http/HttpResource.java index 4a8af90..d4ec9b7 100644 --- a/src/main/java/crud/http/HttpResource.java +++ b/src/main/java/crud/http/HttpResource.java @@ -20,12 +20,13 @@ import com.sun.jersey.api.client.AsyncWebResource; import com.sun.jersey.api.client.ClientResponse; -import crud.DeletableResource; -import crud.ReadableResource; -import crud.Resource; -import crud.UpdatableResource; -import crud.WritableResource; +import crud.spi.DeletableSpec; +import crud.spi.GettableSpec; +import crud.spi.Resource; +import crud.spi.SettableSpec; +import crud.spi.UpdatableSpec; import rx.Observable; +import rx.Observer; import rx.Subscriber; import rx.subscriptions.Subscriptions; @@ -35,10 +36,10 @@ * reading, writing, updating, and deleting. */ public class HttpResource -implements ReadableResource, - DeletableResource, - WritableResource, - UpdatableResource { +implements GettableSpec, + DeletableSpec, + SettableSpec, + UpdatableSpec { private final AsyncWebResource resource; private final ClientRequest requestTemplate; @@ -62,25 +63,49 @@ public Observable delete() { } /** - * Create a new request, as from a copy of the default request, overlaying + * For each request emitted by the given {@link Observable}, create a new + * request, as from a copy of the default request, overlaying * the properties of the given request. Send the resulting message as an - * HTTP {@code PUT} request. + * HTTP {@code PUT} request. Emit all of the results. * * @param resourceState The request to {@code PUT}, expressed as an * addition to the default request. If there is no addition, pass * {@link ClientRequest#empty()}. */ @Override - public Observable write(final ClientRequest resourceState) { + public Observable set(final Observable resourceState) { final Observable.OnSubscribe subscribeAction = new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { - final AsyncWebResource.Builder request = HttpResource.this.resource.getRequestBuilder(); - HttpResource.this.requestTemplate.updateResource(request); - resourceState.updateResource(request); - // Don't pass resourceState to put(): already in request - final Future response = request.put(ResponseListener.adapt(subscriber)); - subscriber.add(Subscriptions.from(response)); + final ResponseListener listener = ResponseListener.adapt(subscriber); + resourceState.subscribe(new Observer() { + @Override + public void onNext(final ClientRequest next) { + if (!subscriber.isUnsubscribed()) { + // FIXME: Fix correlation between request and response + final AsyncWebResource.Builder request = HttpResource.this.resource.getRequestBuilder(); + HttpResource.this.requestTemplate.updateResource(request); + next.updateResource(request); + // Don't pass resourceState to put(): already in request + final Future response = request.put(listener); + subscriber.add(Subscriptions.from(response)); + } + } + + @Override + public void onError(final Throwable e) { + if (!subscriber.isUnsubscribed()) { + subscriber.onError(e); + } + } + + @Override + public void onCompleted() { + if (!subscriber.isUnsubscribed()) { + subscriber.onCompleted(); + } + } + }); } }; final Observable obs = Observable.create(subscribeAction) @@ -98,16 +123,39 @@ public void call(final Subscriber subscriber) { * {@link ClientRequest#empty()}. */ @Override - public Observable update(final ClientRequest update) { + public Observable update(final Observable update) { final Observable.OnSubscribe subscribeAction = new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { - final AsyncWebResource.Builder request = HttpResource.this.resource.getRequestBuilder(); - HttpResource.this.requestTemplate.updateResource(request); - update.updateResource(request); - // Don't pass resourceState to post(): already in request - final Future response = request.post(ResponseListener.adapt(subscriber)); - subscriber.add(Subscriptions.from(response)); + final ResponseListener listener = ResponseListener.adapt(subscriber); + update.subscribe(new Observer() { + @Override + public void onNext(final ClientRequest next) { + if (!subscriber.isUnsubscribed()) { + // FIXME: Fix correlation between request and response + final AsyncWebResource.Builder request = HttpResource.this.resource.getRequestBuilder(); + HttpResource.this.requestTemplate.updateResource(request); + next.updateResource(request); + // Don't pass resourceState to put(): already in request + final Future response = request.post(listener); + subscriber.add(Subscriptions.from(response)); + } + } + + @Override + public void onError(final Throwable e) { + if (!subscriber.isUnsubscribed()) { + subscriber.onError(e); + } + } + + @Override + public void onCompleted() { + if (!subscriber.isUnsubscribed()) { + subscriber.onCompleted(); + } + } + }); } }; final Observable obs = Observable.create(subscribeAction) diff --git a/src/main/java/crud/http/HttpResourceProvider.java b/src/main/java/crud/http/HttpResourceProvider.java index 3d9a10e..2223c17 100644 --- a/src/main/java/crud/http/HttpResourceProvider.java +++ b/src/main/java/crud/http/HttpResourceProvider.java @@ -21,30 +21,34 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; -import crud.DeletableResourceProvider; -import crud.ReadableResourceProvider; -import crud.ResourceProvider; -import crud.UpdatableResourceProvider; -import crud.WritableResourceProvider; +import crud.rsrc.Deletable; +import crud.rsrc.Gettable; +import crud.rsrc.Updatable; +import crud.rsrc.Settable; +import crud.spi.DeletableSetSpec; +import crud.spi.GettableSetSpec; +import crud.spi.ResourceSet; +import crud.spi.UpdatableSetSpec; +import crud.spi.SettableSetSpec; /** - * A {@link ResourceProvider} based on Jersey that provides + * A {@link ResourceSet} based on Jersey that provides * {@link HttpResource}s at given {@link URI}s. These resources are capable of * all four CRUD actions: reading, writing, updating, and deleting. */ public final class HttpResourceProvider -implements ReadableResourceProvider, - DeletableResourceProvider, - WritableResourceProvider, - UpdatableResourceProvider { +implements GettableSetSpec, + DeletableSetSpec, + SettableSetSpec, + UpdatableSetSpec { private final Client restClient; private final ClientRequest requestTemplate; /** - * Create a new {@link ResourceProvider} backed by the given + * Create a new {@link ResourceSet} backed by the given * {@link Client}. */ public static HttpResourceProvider forClient(final Client restClient) { @@ -52,7 +56,7 @@ public static HttpResourceProvider forClient(final Client restClient) { } /** - * Create a new {@link ResourceProvider} backed by the given + * Create a new {@link ResourceSet} backed by the given * {@link Client}. Each request will include all of the elements of the * given request. For example, if all communication should use JSON, you * might pass the result of the following: @@ -71,7 +75,26 @@ public static HttpResourceProvider forClientWithTemplate( } @Override - public HttpResource get(final URI uri) { + public Gettable getter(final URI key) { + return Gettable.from(create(key)); + } + + @Override + public Deletable deleter(final URI key) { + return Deletable.from(create(key)); + } + + @Override + public Settable setter(final URI key) { + return Settable.from(create(key)); + } + + @Override + public Updatable updater(final URI key) { + return Updatable.from(create(key)); + } + + private HttpResource create(final URI uri) { final AsyncWebResource resource = this.restClient.asyncResource(uri); return new HttpResource(resource, this.requestTemplate); } diff --git a/src/main/java/crud/http/package-info.java b/src/main/java/crud/http/package-info.java index d3f9341..540aba1 100644 --- a/src/main/java/crud/http/package-info.java +++ b/src/main/java/crud/http/package-info.java @@ -1,7 +1,6 @@ /** * This package contains implementations of the interfaces in the package - * {@link crud} in terms of the Jersey HTTP - * library. + * {@link crud.spi} in terms of the Jersey HTTP library. * Resources are asynchronous, and use the * {@link java.util.concurrent.ExecutorService} from the * {@link com.sun.jersey.api.client.Client} itself. diff --git a/src/main/java/crud/http/util/FailedResponseOperator.java b/src/main/java/crud/http/util/FailedResponseOperator.java index 8859999..74a6df7 100644 --- a/src/main/java/crud/http/util/FailedResponseOperator.java +++ b/src/main/java/crud/http/util/FailedResponseOperator.java @@ -23,8 +23,8 @@ import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.UniformInterfaceException; -import crud.fluent.FluentReadableResource; -import crud.fluent.FluentReadableResourceProvider; +import crud.rsrc.Gettable; +import crud.rsrc.GettableSet; import rx.Observable; import rx.Observer; import rx.Subscriber; @@ -43,11 +43,13 @@ * that will be detected and retried are those that result in exceptions from * Jersey. * + * TODO: Build this into {@link crud.http.HttpResource} in a better way. + * * @see UniformInterfaceException#getResponse() - * @see FluentReadableResource#lift(rx.Observable.Operator) - * @see FluentReadableResourceProvider#lift(rx.Observable.Operator) - * @see FluentReadableResource#retry(int) - * @see FluentReadableResourceProvider#retry(int) + * @see Gettable#lift(rx.Observable.Operator) + * @see GettableSet#lift(rx.Observable.Operator) + * @see Gettable#retry(int) + * @see GettableSet#retry(int) */ public final class FailedResponseOperator implements Observable.Operator { diff --git a/src/test/java/crud/http/HttpDeletableResourceTest.java b/src/test/java/crud/http/HttpDeletableResourceTest.java index 9d978b7..7dd3dbe 100644 --- a/src/test/java/crud/http/HttpDeletableResourceTest.java +++ b/src/test/java/crud/http/HttpDeletableResourceTest.java @@ -40,11 +40,11 @@ import com.sun.jersey.api.client.async.ITypeListener; import com.sun.jersey.core.header.InBoundHeaders; -import crud.DeletableResourceTest; +import crud.spi.DeletableSpecTest; import rx.Observer; -public class HttpDeletableResourceTest extends DeletableResourceTest { +public class HttpDeletableResourceTest extends DeletableSpecTest { private final AsyncWebResource mockResource = mock(AsyncWebResource.class); private final AsyncWebResource.Builder mockResourceBuilder = mock(AsyncWebResource.Builder.class); diff --git a/src/test/java/crud/http/HttpReadableResourceTest.java b/src/test/java/crud/http/HttpReadableResourceTest.java index ab9a0ac..2c3cd84 100644 --- a/src/test/java/crud/http/HttpReadableResourceTest.java +++ b/src/test/java/crud/http/HttpReadableResourceTest.java @@ -40,11 +40,11 @@ import com.sun.jersey.api.client.async.ITypeListener; import com.sun.jersey.core.header.InBoundHeaders; -import crud.ReadableResourceTest; +import crud.spi.GettableSpecTest; import rx.Observer; -public class HttpReadableResourceTest extends ReadableResourceTest { +public class HttpReadableResourceTest extends GettableSpecTest { private final AsyncWebResource mockResource = mock(AsyncWebResource.class); private final AsyncWebResource.Builder mockResourceBuilder = mock(AsyncWebResource.Builder.class); diff --git a/src/test/java/crud/http/HttpUpdatableResourceTest.java b/src/test/java/crud/http/HttpUpdatableResourceTest.java index 04fdb7c..a8b70f9 100644 --- a/src/test/java/crud/http/HttpUpdatableResourceTest.java +++ b/src/test/java/crud/http/HttpUpdatableResourceTest.java @@ -40,11 +40,12 @@ import com.sun.jersey.api.client.async.ITypeListener; import com.sun.jersey.core.header.InBoundHeaders; -import crud.UpdatableResourceTest; +import crud.spi.UpdatableSpecTest; +import rx.Observable; import rx.Observer; -public class HttpUpdatableResourceTest extends UpdatableResourceTest { +public class HttpUpdatableResourceTest extends UpdatableSpecTest { private final AsyncWebResource mockResource = mock(AsyncWebResource.class); private final AsyncWebResource.Builder mockResourceBuilder = mock(AsyncWebResource.Builder.class); @@ -68,7 +69,7 @@ public void setup() { public void subscribeCallsMocks() { // given: final HttpResource resource = createDefaultResource(); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); // when: final ClientResponse response = resource.update(update).toBlocking().single(); @@ -81,8 +82,8 @@ public void subscribeCallsMocks() { @Test public void clientRequestsCopied() { // given: - final ClientRequest mockRequestTemplate = createDefaultUpdate(); - final ClientRequest mockRequest = createDefaultUpdate(); + final ClientRequest mockRequestTemplate = createDefaultUpdate().toBlocking().single(); + final Observable mockRequest = createDefaultUpdate(); final HttpResource resource = new HttpResource(this.mockResource, mockRequestTemplate); // when: @@ -90,7 +91,7 @@ public void clientRequestsCopied() { // then: verify(mockRequestTemplate).updateResource(this.mockResourceBuilder); - verify(mockRequest).updateResource(this.mockResourceBuilder); + verify(mockRequest.toBlocking().single()).updateResource(this.mockResourceBuilder); } @Test @@ -99,7 +100,7 @@ public void httpPostErrorCallsOnError() throws InterruptedException { // given: final RuntimeException expectedException = new IllegalStateException("mock failure"); final HttpResource resource = createDefaultResource(); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); final AtomicBoolean failed = new AtomicBoolean(); @@ -134,7 +135,7 @@ public void futureGetErrorCallsOnError() throws InterruptedException { // given: final RuntimeException expectedException = new IllegalStateException("mock exception"); final HttpResource resource = createDefaultResource(); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); final String success = "success"; final AtomicReference successOrFail = new AtomicReference<>("never called"); @@ -174,7 +175,7 @@ public void observerOnNextErrorClosesResponse() throws InterruptedException { // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); // when: whenResourceUpdateThenReturn(mockResponse); @@ -189,7 +190,7 @@ public void observerOnCompletedErrorClosesResponse() throws InterruptedException // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); // when: whenResourceUpdateThenReturn(mockResponse); @@ -204,7 +205,7 @@ public void observerOnNextAndOnErrorErrorsClosesResponse() throws InterruptedExc // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); // when: whenResourceUpdateThenReturn(mockResponse); @@ -219,7 +220,7 @@ public void observerOnCompletedAndOnErrorErrorsClosesResponse() throws Interrupt // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest update = createDefaultUpdate(); + final Observable update = createDefaultUpdate(); // when: whenResourceUpdateThenReturn(mockResponse); @@ -235,8 +236,8 @@ protected HttpResource createDefaultResource() { } @Override - protected ClientRequest createDefaultUpdate() { - return mock(ClientRequest.class); + protected Observable createDefaultUpdate() { + return Observable.just(mock(ClientRequest.class)); } private static ClientResponse createResponse() { diff --git a/src/test/java/crud/http/HttpWritableResourceTest.java b/src/test/java/crud/http/HttpWritableResourceTest.java index f6a0486..9a4fb36 100644 --- a/src/test/java/crud/http/HttpWritableResourceTest.java +++ b/src/test/java/crud/http/HttpWritableResourceTest.java @@ -41,11 +41,12 @@ import com.sun.jersey.api.client.async.ITypeListener; import com.sun.jersey.core.header.InBoundHeaders; -import crud.WritableResourceTest; +import crud.spi.SettableSpecTest; +import rx.Observable; import rx.Observer; -public class HttpWritableResourceTest extends WritableResourceTest { +public class HttpWritableResourceTest extends SettableSpecTest { private final AsyncWebResource mockResource = mock(AsyncWebResource.class); private final AsyncWebResource.Builder mockResourceBuilder = mock(AsyncWebResource.Builder.class); @@ -69,10 +70,10 @@ public void setup() { public void subscribeCallsMocks() { // given: final HttpResource resource = createDefaultResource(); - final ClientRequest newValue = createDefaultResourceState(); + final Observable newValue = createDefaultResourceState(); // when: - final ClientResponse response = resource.write(newValue).toBlocking().single(); + final ClientResponse response = resource.set(newValue).toBlocking().single(); // then: assertSame(this.expectedResponse, response); @@ -82,16 +83,16 @@ public void subscribeCallsMocks() { @Test public void clientRequestsCopied() { // given: - final ClientRequest mockRequestTemplate = createDefaultResourceState(); - final ClientRequest mockRequest = createDefaultResourceState(); + final ClientRequest mockRequestTemplate = createDefaultResourceState().toBlocking().single(); + final Observable mockRequest = createDefaultResourceState(); final HttpResource resource = new HttpResource(this.mockResource, mockRequestTemplate); // when: - resource.write(mockRequest).subscribe(); + resource.set(mockRequest).subscribe(); // then: verify(mockRequestTemplate).updateResource(this.mockResourceBuilder); - verify(mockRequest).updateResource(this.mockResourceBuilder); + verify(mockRequest.toBlocking().single()).updateResource(this.mockResourceBuilder); } @Test @@ -100,7 +101,7 @@ public void httpPutErrorCallsOnError() throws InterruptedException { // given: final RuntimeException expectedException = new IllegalStateException("mock failure"); final HttpResource resource = createDefaultResource(); - final ClientRequest newState = createDefaultResourceState(); + final Observable newState = createDefaultResourceState(); final AtomicBoolean failed = new AtomicBoolean(); @@ -108,7 +109,7 @@ public void httpPutErrorCallsOnError() throws InterruptedException { // when: when(this.mockResourceBuilder.put(any(ITypeListener.class))).thenThrow(expectedException); - subscribeAndWait(resource.write(newState), 1, new Observer() { + subscribeAndWait(resource.set(newState), 1, new Observer() { @Override public void onNext(final ClientResponse response) { failed.set(true); @@ -135,7 +136,7 @@ public void futureGetErrorCallsOnError() throws InterruptedException { // given: final RuntimeException expectedException = new IllegalStateException("mock exception"); final HttpResource resource = createDefaultResource(); - final ClientRequest newState = createDefaultResourceState(); + final Observable newState = createDefaultResourceState(); final String success = "success"; final AtomicReference successOrFail = new AtomicReference<>("never called"); @@ -145,7 +146,7 @@ public void futureGetErrorCallsOnError() throws InterruptedException { // when: when(this.mockResourceBuilder.put(any(ITypeListener.class))) .thenAnswer(new ListenerInvokingAnswer(expectedException)); - subscribeAndWait(resource.write(newState), 1, new Observer() { + subscribeAndWait(resource.set(newState), 1, new Observer() { @Override public void onNext(final ClientResponse response) { successOrFail.set("onNext called"); @@ -175,11 +176,11 @@ public void observerOnNextErrorClosesResponse() throws InterruptedException { // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest newState = createDefaultResourceState(); + final Observable newState = createDefaultResourceState(); // when: whenResourceWriteThenReturn(mockResponse); - subscribeWithOnNextFailure(resource.write(newState)); + subscribeWithOnNextFailure(resource.set(newState)); // then: verify(mockResponse).close(); @@ -190,11 +191,11 @@ public void observerOnCompletedErrorClosesResponse() throws InterruptedException // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest newState = createDefaultResourceState(); + final Observable newState = createDefaultResourceState(); // when: whenResourceWriteThenReturn(mockResponse); - subscribeWithOnCompletedFailure(resource.write(newState)); + subscribeWithOnCompletedFailure(resource.set(newState)); // then: verify(mockResponse).close(); @@ -205,11 +206,11 @@ public void observerOnNextAndOnErrorErrorsClosesResponse() throws InterruptedExc // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest newState = createDefaultResourceState(); + final Observable newState = createDefaultResourceState(); // when: whenResourceWriteThenReturn(mockResponse); - subscribeWithOnNextAndOnErrorFailures(resource.write(newState)); + subscribeWithOnNextAndOnErrorFailures(resource.set(newState)); // then: verify(mockResponse).close(); @@ -220,11 +221,11 @@ public void observerOnCompletedAndOnErrorErrorsClosesResponse() throws Interrupt // given: final HttpResource resource = createDefaultResource(); final ClientResponse mockResponse = mock(ClientResponse.class); - final ClientRequest newState = createDefaultResourceState(); + final Observable newState = createDefaultResourceState(); // when: whenResourceWriteThenReturn(mockResponse); - subscribeWithOnCompletedAndOnErrorFailures(resource.write(newState)); + subscribeWithOnCompletedAndOnErrorFailures(resource.set(newState)); // then: verify(mockResponse).close(); @@ -236,8 +237,8 @@ protected HttpResource createDefaultResource() { } @Override - protected ClientRequest createDefaultResourceState() { - return mock(ClientRequest.class); + protected Observable createDefaultResourceState() { + return Observable.just(mock(ClientRequest.class)); } private static ClientResponse createResponse() { diff --git a/src/test/java/crud/http/example/AssetApplication.java b/src/test/java/crud/http/example/AssetApplication.java index 8b3e621..05d31a7 100644 --- a/src/test/java/crud/http/example/AssetApplication.java +++ b/src/test/java/crud/http/example/AssetApplication.java @@ -16,6 +16,9 @@ import java.util.UUID; +import crud.rsrc.Gettable; +import crud.rsrc.Settable; +import rx.Observable; import rx.Observer; @@ -24,21 +27,22 @@ */ class AssetApplication { - private final AssetResourceProvider assetProvider; + private final AssetSet assetProvider; - public AssetApplication(final AssetResourceProvider isThisAWebServiceIDontCare) { + public AssetApplication(final AssetSet isThisAWebServiceIDontCare) { this.assetProvider = isThisAWebServiceIDontCare; } public void processAsset(final UUID assetId) { - final AssetResource resource = this.assetProvider.get(assetId); - resource.get().subscribe(new Observer() { + final Gettable reader = this.assetProvider.getter(assetId); + final Settable writer = this.assetProvider.setter(assetId); + reader.get().subscribe(new Observer() { @Override public void onNext(final Asset asset) { System.out.println("Got the asset " + assetId); - final Asset betterAsset = new Asset(assetId, 42L); - resource.write(betterAsset).subscribe(); + final Observable betterAsset = Observable.just(new Asset(assetId, 42L)); + writer.set(betterAsset).subscribe(); } @Override diff --git a/src/test/java/crud/http/example/AssetResource.java b/src/test/java/crud/http/example/AssetResource.java index cc9382b..f3c3800 100644 --- a/src/test/java/crud/http/example/AssetResource.java +++ b/src/test/java/crud/http/example/AssetResource.java @@ -14,9 +14,9 @@ */ package crud.http.example; -import crud.ReadableResource; -import crud.Resource; -import crud.WritableResource; +import crud.spi.GettableSpec; +import crud.spi.Resource; +import crud.spi.SettableSpec; /** @@ -24,6 +24,6 @@ * particular {@link Asset}. Assets do not support partial updates or * deletion. */ -interface AssetResource extends ReadableResource, WritableResource { +interface AssetResource extends GettableSpec, SettableSpec { // empty } diff --git a/src/test/java/crud/http/example/AssetResourceProvider.java b/src/test/java/crud/http/example/AssetSet.java similarity index 52% rename from src/test/java/crud/http/example/AssetResourceProvider.java rename to src/test/java/crud/http/example/AssetSet.java index 12109e4..024fb2f 100644 --- a/src/test/java/crud/http/example/AssetResourceProvider.java +++ b/src/test/java/crud/http/example/AssetSet.java @@ -16,32 +16,34 @@ import java.util.UUID; -import crud.ReadableResource; -import crud.ReadableResourceProvider; -import crud.Resource; -import crud.ResourceProvider; -import crud.WritableResource; -import crud.WritableResourceProvider; -import crud.fluent.FluentReadableResourceProvider; -import crud.fluent.FluentWritableResourceProvider; +import crud.rsrc.Gettable; +import crud.rsrc.GettableSet; +import crud.rsrc.Settable; +import crud.rsrc.SettableSet; +import crud.spi.GettableSetSpec; +import crud.spi.GettableSpec; +import crud.spi.Resource; +import crud.spi.ResourceSet; +import crud.spi.SettableSetSpec; +import crud.spi.SettableSpec; import rx.Observable; import rx.functions.Func1; /** - * A {@link ResourceProvider} for retrieving readable and writable + * A {@link ResourceSet} for retrieving readable and writable * {@link Asset}s, encapsulated by {@link AssetResource}. */ -class AssetResourceProvider -implements ReadableResourceProvider, WritableResourceProvider { +class AssetSet +implements GettableSetSpec, SettableSetSpec { - private final ReadableResourceProvider readDelegate; - private final WritableResourceProvider writeDelegate; + private final GettableSetSpec readDelegate; + private final SettableSetSpec writeDelegate; /** - * Wrap a pair of {@link ResourceProvider}s with a new - * AssetResourceProvider. These input providers might be, for example, a + * Wrap a pair of {@link ResourceSet}s with a new + * AssetSet. These input providers might be, for example, a * {@link crud.http.HttpResourceProvider}, if the Assets are to * be backed by a web service. However, any backing providers will do, * provided there is some way to transform their inputs and outputs @@ -52,23 +54,48 @@ class AssetResourceProvider * @param The type of the values written by the write delegate. * @param The type of the response from the write delegate. */ - public static AssetResourceProvider create( - final ReadableResourceProvider readDelegate, - final WritableResourceProvider writeDelegate, + public static AssetSet create( + final GettableSetSpec readDelegate, + final SettableSetSpec writeDelegate, final Func1 keyAdapter, final Func1 assetReadMapper, final Func1 assetWriteMapper, final Func1 responseMapper) { - final FluentReadableResourceProvider reader - = FluentReadableResourceProvider.from(readDelegate) + final GettableSet reader + = GettableSet.from(readDelegate) .adaptKey(keyAdapter) - .mapValue(assetReadMapper); - final FluentWritableResourceProvider writer - = FluentWritableResourceProvider.from(writeDelegate) + .mapValue(new Func1, Observable>() { + @Override + public Observable call(final Observable value) { + return value.map(assetReadMapper); + } + }); + final SettableSet writer + = SettableSet.from(writeDelegate) .adaptKey(keyAdapter) - .adaptNewValue(assetWriteMapper) - .mapResponse(responseMapper); - return new AssetResourceProvider(reader, writer); + .adaptNewValue(new Func1, Observable>() { + @Override + public Observable call(final Observable asset) { + return asset.map(assetWriteMapper); + } + }) + .mapResponse(new Func1, Observable>() { + @Override + public Observable call(final Observable t1) { + return t1.map(responseMapper); + } + }); + return new AssetSet(reader, writer); + } + + @Override + public Settable setter(final UUID key) { + return Settable.from(create(key)); + } + + @Override + public Gettable getter(final UUID key) { + return Gettable.from(create(key)); } /** @@ -76,28 +103,27 @@ public static AssetResourceProvider create( * given ID. The state of that Asset may be read or written using * that Resource. */ - @Override - public AssetResource get(final UUID assetId) { - final ReadableResource readRsrc = this.readDelegate.get(assetId); - final WritableResource writeRsrc = this.writeDelegate.get(assetId); + private AssetResource create(final UUID assetId) { + final GettableSpec readRsrc = this.readDelegate.getter(assetId); + final SettableSpec writeRsrc = this.writeDelegate.setter(assetId); return new AssetResourceImpl(writeRsrc, readRsrc); } - private AssetResourceProvider( - final ReadableResourceProvider readDelegate, - final WritableResourceProvider writeDelegate) { + private AssetSet( + final GettableSetSpec readDelegate, + final SettableSetSpec writeDelegate) { this.readDelegate = readDelegate; this.writeDelegate = writeDelegate; } private static final class AssetResourceImpl implements AssetResource { - private final WritableResource writeRsrc; - private final ReadableResource readRsrc; + private final SettableSpec writeRsrc; + private final GettableSpec readRsrc; private AssetResourceImpl( - final WritableResource writeRsrc, - final ReadableResource readRsrc) { + final SettableSpec writeRsrc, + final GettableSpec readRsrc) { this.writeRsrc = writeRsrc; this.readRsrc = readRsrc; } @@ -108,8 +134,8 @@ public Observable get() { } @Override - public Observable write(final Asset newValue) { - return this.writeRsrc.write(newValue); + public Observable set(final Observable newValue) { + return this.writeRsrc.set(newValue); } // Every concrete Resource class should override equals() and hashCode(). diff --git a/src/test/java/crud/http/example/ExampleApplicationContext.java b/src/test/java/crud/http/example/ExampleApplicationContext.java index ba9b4ac..6ab921c 100644 --- a/src/test/java/crud/http/example/ExampleApplicationContext.java +++ b/src/test/java/crud/http/example/ExampleApplicationContext.java @@ -23,12 +23,13 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; -import crud.ResourceProvider; -import crud.fluent.FluentReadableResourceProvider; -import crud.fluent.FluentWritableResourceProvider; import crud.http.ClientRequest; import crud.http.HttpResourceProvider; import crud.http.util.FailedResponseOperator; +import crud.rsrc.GettableSet; +import crud.rsrc.SettableSet; +import crud.spi.ResourceSet; +import rx.Observable; import rx.functions.Func1; @@ -114,8 +115,15 @@ public Boolean call(final ClientResponse input) { } }; + private final Func1, Observable> retryServerErrors = new Func1, Observable>() { + @Override + public Observable call(final Observable t1) { + return t1.lift(FailedResponseOperator.serverErrors()).retry(3); + } + }; + /** - * Assemble the {@link ResourceProvider} for {@link Asset}s by indicating: + * Assemble the {@link ResourceSet} for {@link Asset}s by indicating: *
    *
  1. how to read them (i.e. from a web service),
  2. *
  3. how to write them (i.e. to a web service),
  4. @@ -123,11 +131,11 @@ public Boolean call(final ClientResponse input) { * providers and our application-specific language of "assets". *
*/ - public final AssetResourceProvider assetProvider = AssetResourceProvider.create( + public final AssetSet assetProvider = AssetSet.create( // Retry all server errors on GET up to 3 times: - FluentReadableResourceProvider.from(restResource).lift(FailedResponseOperator.serverErrors()).retry(3), + GettableSet.from(restResource).mapValue(retryServerErrors), // Retry all server errors on PUT up to 3 times: - FluentWritableResourceProvider.from(restResource).lift(FailedResponseOperator.serverErrors()).retry(3), + SettableSet.from(restResource).mapResponse(retryServerErrors), urlBuilder, assetDecoder, assetEncoder,