com.emc.atmos.api.jersey.AtmosApiClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of atmos-client Show documentation
Show all versions of atmos-client Show documentation
EMC Atmos Client for Java - provides REST access to object data on EMC platforms using the Atmos API.
/*
* BSD 3-Clause License
*
* Copyright (c) 2013-2018, Dell EMC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.emc.atmos.api.jersey;
import com.emc.atmos.AtmosException;
import com.emc.atmos.api.*;
import com.emc.atmos.api.bean.*;
import com.emc.atmos.api.multipart.MultipartEntity;
import com.emc.atmos.api.request.*;
import com.emc.util.HttpUtil;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.ClientFilter;
import org.apache.log4j.Logger;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* Reference implementation of AtmosApi.
*
* This implementation uses the JAX-RS reference implementation (Jersey) as it's REST client. When sending or
* receiving data, the following content handlers are supported by default. You may add your own
* MessageBodyReader/MessageBodyWriter implementations by using the optional constructor, however be sure to return a
* positive value from MessageBodyWriter.getSize() as this sets the content-length of the request, which is required by
* Atmos. Be sure to use the appropriate content-type associated with each object type or the handlers will not
* understand the request.
*
*
* Object Type (class) Expected Content-Type(s)
* byte[] *any*
* java.lang.String *any*
* java.io.File (send-only) *any*
* java.io.InputStream (send-only) *any*
* com.emc.atmos.api.BufferSegment (send-only) *any*
* any annotated JAXB root element bean text/xml, application/xml
* com.emc.atmos.api.multipart.MultipartEntity (receive-only) multipart/*
*
*
* Also keep in mind that you can always send/receive byte[] and do your own conversion as that has always been
* supported.
*
* Note: this implementation is not supported with Atmos versions below 1.4.2.
*
* To use, simply pass a new AtmosConfig object to the constructor like so:
*
* URI atmosEndpoint = new URI( "http://api.atmosonline.com" ); // or use your private cloud endpoint/load balancer
* AtmosApi atmos = new AtmosApiClient( new AtmosConfig( "my_full_token_id", "my_secret_key", atmosEndpoint ) );
*
*
* You can also specify multiple endpoints and have each request round-robin between them like so:
*
* URI endpoint1 = new URI( "https://10.0.0.101" ); // 1st Atmos node
* URI endpoint2 = new URI( "https://10.0.0.102" ); // 2nd Atmos node
* URI endpoint3 = new URI( "https://10.0.0.103" ); // 3rd Atmos node
* URI endpoint4 = new URI( "https://10.0.0.104" ); // 4th Atmos node
* AtmosApi atmos = new AtmosApiClient( new AtmosConfig( "my_full_token_id", "my_secret_key",
* endpoint1, endpoint2, endpoint3, endpoint4 ) );
*
*
* To create an object, simply pass the object in to one of the createObject methods. The object type must be one of
* the supported types above.
*
* String stringContent = "Hello World!";
* ObjectId oid1 = atmos.createObject( stringContent, "text/plain" );
*
* File fileContent = new File( "spreadsheet.xls" );
* ObjectId oid2 = atmos.createObject( fileContent, "application/vnd.ms-excel" );
*
* byte[] binaryContent;
* ... // load binary content to store as an object
* ObjectId oid3 = atmos.createObject( binaryContent, null ); // default content-type is application/octet-stream
*
*
* To read an object, specify the type of object you want to receive from a readObject method. The same rules apply to
* this type.
*
* String stringContent = atmos.readObject( oid1, String.class );
*
* byte[] fileContent = atmos.readObject( oid2, byte[].class );
* // do something with file content (stream to client? save in local filesystem?)
*
* byte[] binaryContent = atmos.readObject( oid3, byte[].class );
*
*/
public class AtmosApiClient extends AbstractAtmosApi {
private static final Logger l4j = Logger.getLogger( AtmosApiClient.class );
protected Client client;
protected Client client100;
public AtmosApiClient( AtmosConfig config ) {
this( config, (List>>) null, null );
}
public AtmosApiClient( AtmosConfig config,
List>> readers,
List>> writers ) {
this( config,
JerseyApacheUtil.createApacheClient( config, false, readers, writers ),
JerseyApacheUtil.createApacheClient( config, true, readers, writers ) );
}
protected AtmosApiClient( AtmosConfig config, Client client, Client client100 ) {
super( config );
this.client = client;
// without writing our own client implementation, the only way to discriminate requests that enable
// Expect: 100-continue behavior is to have two clients; one with the feature enabled and one without.
this.client100 = client100;
}
/**
* Adds a ClientFilter to the Jersey client used to make REST requests. Useful to provide your own in-line content
* or header manipulation.
*/
public void addClientFilter( ClientFilter filter ) {
client.addFilter( filter );
client100.addFilter( filter );
}
@Override
public ServiceInformation getServiceInformation() {
ClientResponse response = client.resource( config.resolvePath( "service", null ) ).get( ClientResponse.class );
ServiceInformation serviceInformation = response.getEntity( ServiceInformation.class );
String featureString = response.getHeaders().getFirst( RestUtil.XHEADER_FEATURES );
if ( featureString != null ) {
for ( String feature : featureString.split( "," ) )
serviceInformation.addFeatureFromHeaderName( feature.trim() );
}
// legacy
String utf8String = response.getHeaders().getFirst( RestUtil.XHEADER_SUPPORT_UTF8 );
if ( utf8String != null && Boolean.valueOf( utf8String ) )
serviceInformation.addFeature( ServiceInformation.Feature.Utf8 );
response.close();
return serviceInformation;
}
@Override
public long calculateServerClockSkew() {
WebResource resource = client.resource( config.resolvePath( "", null ) );
resource.setProperty( ErrorFilter.NO_EXCEPTIONS, true );
ClientResponse response = resource.get( ClientResponse.class );
if ( response.getResponseDate() == null )
throw new AtmosException( "Response date is null", response.getStatus() );
config.setServerClockSkew( System.currentTimeMillis() - response.getResponseDate().getTime() );
response.close();
return config.getServerClockSkew();
}
@Override
public CreateObjectResponse createObject( CreateObjectRequest request ) {
ClientResponse response = build( request ).post( ClientResponse.class, getContent( request ) );
response.close();
return fillResponse( new CreateObjectResponse(), response );
}
@Override
public ReadObjectResponse readObject( ReadObjectRequest request, Class objectType ) throws IOException {
if ( request.getRanges() != null && request.getRanges().size() > 1
&& !MultipartEntity.class.isAssignableFrom( objectType ) )
l4j.warn( "multiple ranges imply a multi-part response. you should ask for MultipartEntity instead of " +
objectType.getSimpleName() );
ClientResponse response = build( request ).get( ClientResponse.class );
ReadObjectResponse ret = new ReadObjectResponse( response.getEntity( objectType ) );
response.close();
return fillResponse( ret, response );
}
@Override
public ReadObjectResponse readObjectStream( ObjectIdentifier identifier, Range range ) {
ClientResponse response = build( new ReadObjectRequest().identifier( identifier ).ranges( range ) )
.get( ClientResponse.class );
return fillResponse( new ReadObjectResponse( response.getEntityInputStream() ), response );
}
@Override
public BasicResponse updateObject( UpdateObjectRequest request ) {
ClientResponse response = build( request ).put( ClientResponse.class, getContent( request ) );
response.close();
return fillResponse( new BasicResponse(), response );
}
@Override
public void delete( ObjectIdentifier identifier ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), null );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
builder.delete();
}
@Override
public ObjectId createDirectory( ObjectPath path ) {
if ( !path.isDirectory() ) throw new AtmosException( "Path must be a directory" );
CreateObjectRequest request = new CreateObjectRequest().identifier( path );
ClientResponse response = build( request ).post( ClientResponse.class );
response.close();
return RestUtil.parseObjectId( response.getLocation().getPath() );
}
@Override
public ObjectId createDirectory( ObjectPath path, Acl acl, Metadata... metadata ) {
if ( !path.isDirectory() ) throw new AtmosException( "Path must be a directory" );
CreateObjectRequest request = new CreateObjectRequest().identifier( path ).acl( acl );
request.userMetadata( metadata );
ClientResponse response = build( request ).post( ClientResponse.class );
response.close();
return RestUtil.parseObjectId( response.getLocation().getPath() );
}
@Override
public ListDirectoryResponse listDirectory( ListDirectoryRequest request ) {
if ( !request.getPath().isDirectory() ) throw new AtmosException( "Path must be a directory" );
ClientResponse response = build( request ).get( ClientResponse.class );
request.setToken( response.getHeaders().getFirst( RestUtil.XHEADER_TOKEN ) );
if ( request.getToken() != null )
l4j.info( "Results truncated. Call listDirectory again for next page of results." );
ListDirectoryResponse ret = response.getEntity( ListDirectoryResponse.class );
response.close();
return fillResponse( ret, response );
}
@Override
public void move( ObjectPath oldPath, ObjectPath newPath, boolean overwrite ) {
WebResource resource = client.resource( config.resolvePath( oldPath.getRelativeResourcePath(), "rename" ) );
WebResource.Builder builder = resource.getRequestBuilder();
builder.header( RestUtil.XHEADER_PATH,
config.isEncodeUtf8() ? HttpUtil.encodeUtf8( newPath.getPath() ) : newPath.getPath() );
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
if ( overwrite ) builder.header( RestUtil.XHEADER_FORCE, "true" );
// workaround for clients that set a default content-type for POSTs
builder.type( RestUtil.TYPE_DEFAULT );
builder.post();
}
@Override
public Map getUserMetadataNames( ObjectIdentifier identifier ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "metadata/tags" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
ClientResponse response = builder.get( ClientResponse.class );
Map metaNames = new TreeMap();
String nameString = response.getHeaders().getFirst( RestUtil.XHEADER_TAGS );
if ( nameString != null ) {
for ( String name : nameString.split( "," ) )
metaNames.put( config.isEncodeUtf8() ? HttpUtil.decodeUtf8( name.trim() ) : name.trim(), false );
}
nameString = response.getHeaders().getFirst( RestUtil.XHEADER_LISTABLE_TAGS );
if ( nameString != null ) {
for ( String name : nameString.split( "," ) )
metaNames.put( config.isEncodeUtf8() ? HttpUtil.decodeUtf8( name.trim() ) : name.trim(), true );
}
response.close();
return metaNames;
}
@Override
public Map getUserMetadata( ObjectIdentifier identifier, String... metadataNames ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "metadata/user" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
if ( metadataNames != null ) {
for ( String name : metadataNames ) {
if ( config.isEncodeUtf8() ) name = HttpUtil.encodeUtf8( name );
builder.header( RestUtil.XHEADER_TAGS, name );
}
}
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
ClientResponse response = builder.get( ClientResponse.class );
Map metaMap = new TreeMap();
metaMap.putAll( RestUtil.parseMetadataHeader( response.getHeaders().getFirst( RestUtil.XHEADER_META ),
false, config.isEncodeUtf8() ) );
metaMap.putAll( RestUtil.parseMetadataHeader( response.getHeaders().getFirst( RestUtil.XHEADER_LISTABLE_META ),
true, config.isEncodeUtf8() ) );
response.close();
return metaMap;
}
@Override
public Map getSystemMetadata( ObjectIdentifier identifier, String... metadataNames ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "metadata/system" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
if ( metadataNames != null ) {
for ( String name : metadataNames ) {
if ( config.isEncodeUtf8() ) name = HttpUtil.encodeUtf8( name );
builder.header( RestUtil.XHEADER_TAGS, name );
}
}
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
ClientResponse response = builder.get( ClientResponse.class );
response.close();
return RestUtil.parseMetadataHeader( response.getHeaders().getFirst( RestUtil.XHEADER_META ),
false, config.isEncodeUtf8() );
}
@Override
public boolean objectExists( ObjectIdentifier identifier ) {
try {
getSystemMetadata( identifier );
return true;
} catch ( AtmosException e ) {
if ( e.getErrorCode() == 1003 ) return false;
throw e;
}
}
@Override
public ObjectMetadata getObjectMetadata( ObjectIdentifier identifier ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), null );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
ClientResponse response = builder.head();
Acl acl = new Acl( RestUtil.parseAclHeader( response.getHeaders().getFirst( RestUtil.XHEADER_USER_ACL ) ),
RestUtil.parseAclHeader( response.getHeaders()
.getFirst( RestUtil.XHEADER_GROUP_ACL ) ) );
Map metaMap = new TreeMap();
metaMap.putAll( RestUtil.parseMetadataHeader( response.getHeaders().getFirst( RestUtil.XHEADER_META ),
false, config.isEncodeUtf8() ) );
metaMap.putAll( RestUtil.parseMetadataHeader( response.getHeaders()
.getFirst( RestUtil.XHEADER_LISTABLE_META ),
true, config.isEncodeUtf8() ) );
String wsChecksumHeader = response.getHeaders().getFirst( RestUtil.XHEADER_WSCHECKSUM );
ChecksumValue wsChecksum = wsChecksumHeader == null ? null : new ChecksumValueImpl( wsChecksumHeader );
String serverChecksumHeader = response.getHeaders().getFirst( RestUtil.XHEADER_CONTENT_CHECKSUM );
ChecksumValue serverChecksum = serverChecksumHeader == null ? null : new ChecksumValueImpl( serverChecksumHeader );
String retentionPeriod = response.getHeaders().getFirst( RestUtil.XHEADER_RETENTION_PERIOD );
response.close();
ObjectMetadata metadata = new ObjectMetadata( metaMap, acl, response.getType().toString(), wsChecksum, serverChecksum );
if ( retentionPeriod != null ) metadata.setRetentionPeriod( Long.parseLong( retentionPeriod ) );
metadata.setRetentionPolicy( response.getHeaders().getFirst( RestUtil.XHEADER_RETENTION_POLICY ) );
metadata.setETag( response.getHeaders().getFirst( RestUtil.HEADER_ETAG ) );
return metadata;
}
@Override
public void setUserMetadata( ObjectIdentifier identifier, Metadata... metadata ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "metadata/user" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
for ( Metadata oneMetadata : metadata ) {
if ( oneMetadata.isListable() ) {
builder.header( RestUtil.XHEADER_LISTABLE_META,
config.isEncodeUtf8() ? oneMetadata.toASCIIString() : oneMetadata.toString() );
} else {
builder.header( RestUtil.XHEADER_META,
config.isEncodeUtf8() ? oneMetadata.toASCIIString() : oneMetadata.toString() );
}
}
// workaround for clients that set a default content-type for POSTs
builder.type( RestUtil.TYPE_DEFAULT );
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
builder.post();
}
@Override
public void deleteUserMetadata( ObjectIdentifier identifier, String... names ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "metadata/user" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
for ( String name : names ) {
if ( config.isEncodeUtf8() ) name = HttpUtil.encodeUtf8( name );
builder.header( RestUtil.XHEADER_TAGS, name );
}
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
builder.delete();
}
@Override
public Set listMetadata( String metadataName ) {
URI uri = config.resolvePath( "objects", "listabletags" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( metadataName != null )
builder.header( RestUtil.XHEADER_TAGS,
config.isEncodeUtf8() ? HttpUtil.encodeUtf8( metadataName ) : metadataName );
if ( config.isEncodeUtf8() ) builder.header( RestUtil.XHEADER_UTF8, "true" );
ClientResponse response = builder.get( ClientResponse.class );
String headerValue = response.getHeaders().getFirst( RestUtil.XHEADER_LISTABLE_TAGS );
Set names = new TreeSet();
if ( headerValue == null ) return names;
for ( String name : headerValue.split( "," ) )
names.add( config.isEncodeUtf8() ? HttpUtil.decodeUtf8( name.trim() ) : name.trim() );
response.close();
return names;
}
@Override
public ListObjectsResponse listObjects( ListObjectsRequest request ) {
if ( request.getMetadataName() == null )
throw new AtmosException( "You must specify the name of a listable piece of metadata" );
ClientResponse response;
try {
response = build( request ).get( ClientResponse.class );
} catch ( AtmosException e ) {
// if the name doesn't exist, return an empty result instead of throwing an exception (requested by users)
if ( e.getErrorCode() != 1003 ) throw e;
ListObjectsResponse lor = new ListObjectsResponse();
lor.setEntries( new ArrayList() );
return lor;
}
request.setToken( response.getHeaders().getFirst( RestUtil.XHEADER_TOKEN ) );
if ( request.getToken() != null )
l4j.info( "Results truncated. Call listObjects again for next page of results." );
ListObjectsResponse ret = response.getEntity( ListObjectsResponse.class );
response.close();
return fillResponse( ret, response );
}
@Override
public Acl getAcl( ObjectIdentifier identifier ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "acl" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
ClientResponse response = builder.get( ClientResponse.class );
Acl acl = new Acl();
acl.setUserAcl( RestUtil.parseAclHeader( response.getHeaders().getFirst( RestUtil.XHEADER_USER_ACL ) ) );
acl.setGroupAcl( RestUtil.parseAclHeader( response.getHeaders().getFirst( RestUtil.XHEADER_GROUP_ACL ) ) );
response.close();
return acl;
}
@Override
public void setAcl( ObjectIdentifier identifier, Acl acl ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "acl" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
if ( acl != null ) {
for ( Object value : acl.getUserAclHeader() ) builder.header( RestUtil.XHEADER_USER_ACL, value );
for ( Object value : acl.getGroupAclHeader() ) builder.header( RestUtil.XHEADER_GROUP_ACL, value );
}
// workaround for clients that set a default content-type for POSTs
builder.type( RestUtil.TYPE_DEFAULT );
builder.post();
}
@Override
public ObjectInfo getObjectInfo( ObjectIdentifier identifier ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "info" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
return builder.get( ObjectInfo.class );
}
@Override
public ObjectId createVersion( ObjectIdentifier identifier ) {
URI uri = config.resolvePath( identifier.getRelativeResourcePath(), "versions" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
if ( identifier instanceof ObjectKey )
builder.header( RestUtil.XHEADER_POOL, ((ObjectKey) identifier).getBucket() );
// workaround for clients that set a default content-type for POSTs
builder.type( RestUtil.TYPE_DEFAULT );
ClientResponse response = builder.post( ClientResponse.class );
response.close();
return RestUtil.parseObjectId( response.getLocation().getPath() );
}
@Override
public ListVersionsResponse listVersions( ListVersionsRequest request ) {
ClientResponse response = build( request ).get( ClientResponse.class );
request.setToken( response.getHeaders().getFirst( RestUtil.XHEADER_TOKEN ) );
if ( request.getToken() != null )
l4j.info( "Results truncated. Call listVersions again for next page of results." );
ListVersionsResponse ret = response.getEntity( ListVersionsResponse.class );
response.close();
return fillResponse( ret, response );
}
@Override
public void restoreVersion( ObjectId objectId, ObjectId versionId ) {
URI uri = config.resolvePath( objectId.getRelativeResourcePath(), "versions" );
WebResource.Builder builder = client.resource( uri ).getRequestBuilder();
builder.header( RestUtil.XHEADER_VERSION_OID, versionId ).put();
}
@Override
public void deleteVersion( ObjectId versionId ) {
client.resource( config.resolvePath( versionId.getRelativeResourcePath(), "versions" ) ).delete();
}
@Override
public CreateAccessTokenResponse createAccessToken( CreateAccessTokenRequest request )
throws MalformedURLException {
ClientResponse response = build( request ).post( ClientResponse.class, request.getPolicy() );
URI tokenUri = config.resolvePath( response.getLocation().getPath(), response.getLocation().getQuery() );
response.close();
return fillResponse( new CreateAccessTokenResponse( tokenUri.toURL() ), response );
}
@Override
public GetAccessTokenResponse getAccessToken( String accessTokenId ) {
URI uri = config.resolvePath( "accesstokens/" + accessTokenId, "info" );
ClientResponse response = client.resource( uri ).get( ClientResponse.class );
GetAccessTokenResponse ret = new GetAccessTokenResponse( response.getEntity( AccessToken.class ) );
response.close();
return fillResponse( ret, response );
}
@Override
public void deleteAccessToken( String accessTokenId ) {
client.resource( config.resolvePath( "accesstokens/" + accessTokenId, null ) ).delete();
}
@Override
public ListAccessTokensResponse listAccessTokens( ListAccessTokensRequest request ) {
ClientResponse response = build( request ).get( ClientResponse.class );
request.setToken( response.getHeaders().getFirst( RestUtil.XHEADER_TOKEN ) );
if ( request.getToken() != null )
l4j.info( "Results truncated. Call listAccessTokens again for next page of results." );
ListAccessTokensResponse ret = response.getEntity( ListAccessTokensResponse.class );
response.close();
return fillResponse( ret, response );
}
@SuppressWarnings("unchecked")
@Override
public GenericResponse execute( PreSignedRequest request, Class resultType, Object content )
throws URISyntaxException {
WebResource.Builder builder = client.resource( request.getUrl().toURI() ).getRequestBuilder();
addHeaders( builder, request.getHeaders() ).type( request.getContentType() );
ClientResponse response = builder.method( request.getMethod(), ClientResponse.class, content );
GenericResponse ret;
if ( InputStream.class.equals( resultType ) ) {
ret = (GenericResponse) new GenericResponse( response.getEntityInputStream() );
} else {
ret = new GenericResponse( response.getEntity( resultType ) );
response.close();
}
return fillResponse( ret, response );
}
protected WebResource.Builder build( Request request ) {
WebResource resource;
if ( request.supports100Continue() && config.isEnableExpect100Continue() && client100 != null ) {
// use client with Expect: 100-continue
l4j.debug( "Expect: 100-continue is enabled for this request" );
resource = client100.resource( config.resolvePath( request.getServiceRelativePath(), request.getQuery() ) );
} else {
resource = client.resource( config.resolvePath( request.getServiceRelativePath(), request.getQuery() ) );
}
WebResource.Builder builder = resource.getRequestBuilder();
if ( request instanceof ContentRequest ) {
ContentRequest contentRequest = (ContentRequest) request;
if ( contentRequest.getContentType() == null ) builder.type( AbstractAtmosApi.DEFAULT_CONTENT_TYPE );
else builder.type( contentRequest.getContentType() );
} else if ( "POST".equals( request.getMethod() ) ) {
// workaround for clients that set a default content-type for POSTs
builder.type( RestUtil.TYPE_DEFAULT );
}
return addHeaders( builder, request.generateHeaders( config.isEncodeUtf8() ) );
}
protected WebResource.Builder addHeaders( WebResource.Builder builder, Map> headers ) {
for ( String name : headers.keySet() ) {
for ( Object value : headers.get( name ) ) {
builder.header( name, value );
}
}
return builder;
}
/**
* Populates a response object with data from the ClientResponse.
*/
protected T fillResponse( T response, ClientResponse clientResponse ) {
Response.StatusType statusType = clientResponse.getStatusInfo();
MediaType type = clientResponse.getType();
URI location = clientResponse.getLocation();
response.setHttpStatus( clientResponse.getStatus() );
response.setHttpMessage( statusType == null ? null : statusType.getReasonPhrase() );
response.setHeaders( clientResponse.getHeaders() );
response.setContentType( type == null ? null : type.toString() );
response.setContentLength( clientResponse.getLength() );
response.setLocation( location == null ? null : location.toString() );
if ( clientResponse.getHeaders() != null ) {
// workaround for Github Issue #3
response.setDate( HttpUtil.safeHeaderParse( clientResponse.getHeaders().getFirst( RestUtil.HEADER_DATE ) ) );
response.setLastModified( HttpUtil.safeHeaderParse( clientResponse.getHeaders().getFirst( RestUtil.HEADER_LAST_MODIFIED ) ) );
response.setETag( clientResponse.getHeaders().getFirst( RestUtil.HEADER_ETAG ) );
}
return response;
}
protected Object getContent( ContentRequest request ) {
Object content = request.getContent();
if ( content == null ) return new byte[0]; // need this to provide Content-Length: 0
else if ( content instanceof InputStream ) {
if ( request.getContentLength() < 0 )
throw new UnsupportedOperationException(
"Content request with input stream must provide content length" );
if ( request.getContentLength() == 0 )
l4j.info( "Content request with input stream and zero-length will not send any data" );
return new MeasuredInputStream( (InputStream) content, request.getContentLength() );
} else return content;
}
@Override
public String createSubtenant(CreateSubtenantRequest request) {
ClientResponse response = build( request ).put( ClientResponse.class );
return response.getHeaders().get( "subtenantID" ).get( 0 );
}
@Override
public void deleteSubtenant( String subtenantId ) {
client.resource( config.resolvePath( "subtenant/" + subtenantId, null ) ).delete();
}
}