diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java b/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java index 06e64fc3ed..2c10a778ba 100644 --- a/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java +++ b/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java @@ -45,6 +45,7 @@ import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders; import org.jclouds.atmos.functions.ParseUserMetadataFromHeaders; import org.jclouds.atmos.functions.ReturnEndpointIfAlreadyExists; import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead; +import org.jclouds.atmos.functions.ThrowIllegalStateExceptionOn400; import org.jclouds.atmos.options.ListOptions; import org.jclouds.atmos.options.PutOptions; import org.jclouds.blobstore.functions.ThrowContainerNotFoundOn404; @@ -116,6 +117,7 @@ public interface AtmosAsyncClient { */ @POST @Path("/{parent}/{name}") + @ExceptionParser(ThrowIllegalStateExceptionOn400.class) @Consumes(MediaType.WILDCARD) ListenableFuture createFile( @PathParam("parent") String parent, diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/functions/ThrowIllegalStateExceptionOn400.java b/apis/atmos/src/main/java/org/jclouds/atmos/functions/ThrowIllegalStateExceptionOn400.java new file mode 100644 index 0000000000..f9e8c6a582 --- /dev/null +++ b/apis/atmos/src/main/java/org/jclouds/atmos/functions/ThrowIllegalStateExceptionOn400.java @@ -0,0 +1,71 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.atmos.functions; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.inject.Inject; +import org.jclouds.atmos.domain.AtmosError; +import org.jclouds.atmos.util.AtmosUtils; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.http.HttpUtils; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Andrei Savu + * @see Error codes section at + */ +public class ThrowIllegalStateExceptionOn400 implements Function { + + private final AtmosUtils utils; + + @Inject + public ThrowIllegalStateExceptionOn400(AtmosUtils utils) { + this.utils = checkNotNull(utils, "utils is null"); + } + + @Override + public Object apply(Exception from) { + if (from instanceof HttpResponseException) { + HttpResponseException exception = (HttpResponseException) from; + if (exception.getResponse().getStatusCode() == 400) { + AtmosError error = parseErrorFromResponse(exception); + + if (error.getCode() == 1016) { + throw new IllegalStateException("The resource you are trying to create\n" + + "already exists.", from); + } + } + } + throw Throwables.propagate(from); + } + + @VisibleForTesting + protected AtmosError parseErrorFromResponse(HttpResponseException responseException) { + HttpResponse response = responseException.getResponse(); + HttpCommand command = responseException.getCommand(); + + byte[] content = HttpUtils.closeClientButKeepContentStream(response); + return utils.parseAtmosErrorFromContent(command, response, new String(content)); + } +} diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/handlers/AtmosClientErrorRetryHandler.java b/apis/atmos/src/main/java/org/jclouds/atmos/handlers/AtmosClientErrorRetryHandler.java index 6225524064..0eef327b1e 100644 --- a/apis/atmos/src/main/java/org/jclouds/atmos/handlers/AtmosClientErrorRetryHandler.java +++ b/apis/atmos/src/main/java/org/jclouds/atmos/handlers/AtmosClientErrorRetryHandler.java @@ -37,6 +37,7 @@ import com.google.inject.Inject; /** * Handles Retryable responses with error codes in the 4xx range * + * @see Error codes section at * @author Adrian Cole */ public class AtmosClientErrorRetryHandler implements HttpRetryHandler { @@ -62,14 +63,14 @@ public class AtmosClientErrorRetryHandler implements HttpRetryHandler { if (response.getStatusCode() == 404 && command.getCurrentRequest().getMethod().equals("DELETE")) { command.incrementFailureCount(); return true; - } else if (response.getStatusCode() == 409 || response.getStatusCode() == 400) { + } else if (response.getStatusCode() == 409) { byte[] content = HttpUtils.closeClientButKeepContentStream(response); // Content can be null in the case of HEAD requests if (content != null) { try { AtmosError error = utils.parseAtmosErrorFromContent(command, response, new String(content)); - if (error.getCode() == 1016) { + if (error.getCode() == 1006) { return backoffHandler.shouldRetryRequest(command, response); } // don't increment count before here, since backoff handler does already diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/util/AtmosUtils.java b/apis/atmos/src/main/java/org/jclouds/atmos/util/AtmosUtils.java index af822753cf..7efa256904 100644 --- a/apis/atmos/src/main/java/org/jclouds/atmos/util/AtmosUtils.java +++ b/apis/atmos/src/main/java/org/jclouds/atmos/util/AtmosUtils.java @@ -27,6 +27,7 @@ import javax.inject.Provider; import org.jclouds.atmos.AtmosClient; import org.jclouds.atmos.blobstore.functions.BlobToObject; import org.jclouds.atmos.domain.AtmosError; +import org.jclouds.atmos.domain.AtmosObject; import org.jclouds.atmos.filters.SignRequest; import org.jclouds.atmos.options.PutOptions; import org.jclouds.atmos.xml.ErrorHandler; @@ -70,8 +71,15 @@ public class AtmosUtils { public static String putBlob(final AtmosClient sync, Crypto crypto, BlobToObject blob2Object, String container, Blob blob, PutOptions options) { final String path = container + "/" + blob.getMetadata().getName(); - deleteAndEnsureGone(sync, path); - sync.createFile(container, blob2Object.apply(blob), options); + final AtmosObject object = blob2Object.apply(blob); + + try { + sync.createFile(container, object, options); + + } catch(IllegalStateException e) { + deleteAndEnsureGone(sync, path); + sync.createFile(container, object, options); + } return path; } diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java index 878e0e7e8f..6dd3e68590 100644 --- a/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java +++ b/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java @@ -35,6 +35,7 @@ import org.jclouds.atmos.functions.ParseObjectFromHeadersAndHttpContent; import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders; import org.jclouds.atmos.functions.ReturnEndpointIfAlreadyExists; import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead; +import org.jclouds.atmos.functions.ThrowIllegalStateExceptionOn400; import org.jclouds.atmos.options.ListOptions; import org.jclouds.atmos.options.PutOptions; import org.jclouds.blobstore.binders.BindBlobToMultipartFormTest; @@ -172,7 +173,7 @@ public class AtmosAsyncClientTest extends BaseAsyncClientTest assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class); assertSaxResponseParserClassEquals(method, null); - assertExceptionParserClassEquals(method, null); + assertExceptionParserClassEquals(method, ThrowIllegalStateExceptionOn400.class); checkFilters(request); } @@ -190,7 +191,7 @@ public class AtmosAsyncClientTest extends BaseAsyncClientTest assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class); assertSaxResponseParserClassEquals(method, null); - assertExceptionParserClassEquals(method, null); + assertExceptionParserClassEquals(method, ThrowIllegalStateExceptionOn400.class); checkFilters(request); } diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosLiveTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosLiveTest.java index d826318341..8b862d7cb8 100644 --- a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosLiveTest.java +++ b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosLiveTest.java @@ -32,6 +32,8 @@ public class AtmosLiveTest extends BaseBlobLiveTest { public AtmosLiveTest() { provider = "atmos"; } + + @Override protected void checkMD5(String container, String name, byte[] md5) { // atmos does not support content-md5 yet assertEquals(view.getBlobStore().blobMetadata(container, name).getContentMetadata().getContentMD5(), null); diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/functions/ThrowIllegalStateExceptionOn400Test.java b/apis/atmos/src/test/java/org/jclouds/atmos/functions/ThrowIllegalStateExceptionOn400Test.java new file mode 100644 index 0000000000..75a2524fac --- /dev/null +++ b/apis/atmos/src/test/java/org/jclouds/atmos/functions/ThrowIllegalStateExceptionOn400Test.java @@ -0,0 +1,54 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.atmos.functions; + +import org.jclouds.atmos.domain.AtmosError; +import org.jclouds.atmos.util.AtmosUtils; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.testng.annotations.Test; + +/** + * @author Andrei Savu + */ +public class ThrowIllegalStateExceptionOn400Test { + + @Test(expectedExceptions = IllegalStateException.class) + public void testResourceAlreadyExists() { + new ThrowIllegalStateExceptionOn400(new AtmosUtils()) { + @Override + protected AtmosError parseErrorFromResponse(HttpResponseException ignore) { + return new AtmosError(1016, "Resource already exists"); + } + }.apply(new HttpResponseException("Resource already exists", null, + HttpResponse.builder().statusCode(400).build(), (Throwable) null)); + + } + + @Test(expectedExceptions = RuntimeException.class) + public void testNotFoundPropagates() { + new ThrowIllegalStateExceptionOn400(new AtmosUtils()).apply(new RuntimeException()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testNullIsBad() { + new ThrowIllegalStateExceptionOn400(new AtmosUtils()).apply(null); + } + +} diff --git a/providers/synaptic-storage/src/test/java/org/jclouds/synaptic/storage/SynapticStorageClientLiveTest.java b/providers/synaptic-storage/src/test/java/org/jclouds/synaptic/storage/SynapticStorageClientLiveTest.java index dd9dfd7744..c8359f08de 100644 --- a/providers/synaptic-storage/src/test/java/org/jclouds/synaptic/storage/SynapticStorageClientLiveTest.java +++ b/providers/synaptic-storage/src/test/java/org/jclouds/synaptic/storage/SynapticStorageClientLiveTest.java @@ -29,4 +29,7 @@ import org.testng.annotations.Test; @Test(groups = "live", sequential = true, testName = "SynapticStorageClientLiveTest") public class SynapticStorageClientLiveTest extends AtmosClientLiveTest { + public SynapticStorageClientLiveTest() { + provider = "synaptic-storage"; + } }