/* * Copyright 2014 EMC Corporation. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0.txt * * or in the "license" file accompanying this file. This file 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 com.emc.atmos.api.test; import com.emc.atmos.AtmosException; import com.emc.atmos.StickyThreadAlgorithm; import com.emc.atmos.api.*; import com.emc.atmos.api.bean.*; import com.emc.atmos.api.jersey.AtmosApiBasicClient; import com.emc.atmos.api.jersey.AtmosApiClient; import com.emc.atmos.api.multipart.MultipartEntity; import com.emc.atmos.api.request.*; import com.emc.atmos.util.AtmosClientFactory; import com.emc.atmos.util.RandomInputStream; import com.emc.atmos.util.ReorderedFormDataContentDisposition; import com.emc.test.util.Concurrent; import com.emc.test.util.ConcurrentJunitRunner; import com.emc.util.StreamUtil; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.ClientRequest; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.filter.ClientFilter; import com.sun.jersey.core.header.FormDataContentDisposition; import com.sun.jersey.multipart.BodyPart; import com.sun.jersey.multipart.FormDataMultiPart; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; import org.junit.*; import org.junit.runner.RunWith; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.ws.rs.core.MediaType; import java.io.*; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @RunWith(ConcurrentJunitRunner.class) @Concurrent public class AtmosApiClientTest { public static Logger l4j = Logger.getLogger( AtmosApiClientTest.class ); /** * Use this as a prefix for namespace object paths and you won't have to clean up after yourself. * This also keeps all test objects under one folder, which is easy to delete should something go awry. */ protected static final String TEST_DIR_PREFIX = "/test_" + AtmosApiClientTest.class.getSimpleName(); protected AtmosConfig config; protected AtmosApi api; protected boolean isVipr = false; protected List<ObjectIdentifier> cleanup = Collections.synchronizedList( new ArrayList<ObjectIdentifier>() ); protected List<ObjectPath> cleanupDirs = Collections.synchronizedList( new ArrayList<ObjectPath>() ); public AtmosApiClientTest() throws Exception { config = AtmosClientFactory.getAtmosConfig(); Assume.assumeTrue("Could not load Atmos configuration", config != null); config.setDisableSslValidation( false ); config.setEnableExpect100Continue( false ); config.setEnableRetry( false ); config.setLoadBalancingAlgorithm( new StickyThreadAlgorithm() ); api = new AtmosApiClient( config ); isVipr = AtmosClientFactory.atmosIsVipr(); } @After public void tearDown() { for ( ObjectIdentifier cleanItem : cleanup ) { try { api.delete( cleanItem ); } catch ( Throwable t ) { System.out.println( "Failed to delete " + cleanItem + ": " + t.getMessage() ); } } try { // if test directories exists, recursively delete them for ( ObjectPath testDir : cleanupDirs ) { deleteRecursively( testDir ); } } catch ( AtmosException e ) { if ( e.getHttpCode() != 404 ) { l4j.warn( "Could not delete test dir: ", e ); } } if(!isVipr) { try { ListAccessTokensResponse response = this.api.listAccessTokens( new ListAccessTokensRequest() ); if ( response.getTokens() != null ) { for ( AccessToken token : response.getTokens() ) { this.api.deleteAccessToken( token.getId() ); } } } catch ( Exception e ) { System.out.println( "Failed to delete access tokens: " + e.getMessage() ); } } } protected void deleteRecursively( ObjectPath path ) { if ( path.isDirectory() ) { ListDirectoryRequest request = new ListDirectoryRequest().path( path ); do { for ( DirectoryEntry entry : this.api.listDirectory( request ).getEntries() ) { deleteRecursively( new ObjectPath( path, entry ) ); } } while ( request.getToken() != null ); } this.api.delete( path ); } protected ObjectPath createTestDir( String name ) { if (!name.endsWith("/")) name = name + "/"; ObjectPath path = new ObjectPath( TEST_DIR_PREFIX + "_" + name ); this.api.createDirectory( path ); cleanupDirs.add( path ); return path; } // // TESTS START HERE // @Test public void testUtf8JavaEncoding() throws Exception { String oneByteCharacters = "Hello"; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String twoByteEscaped = "%D0%90%D0%91%D0%92%D0%93"; String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols String fourByteEscaped = "%F0%A0%9C%8E%F0%A0%9C%B1%F0%A0%9D%B9%F0%A0%B1%93"; Assert.assertEquals( "2-byte characters failed", URLEncoder.encode( twoByteCharacters, "UTF-8" ), twoByteEscaped ); Assert.assertEquals( "4-byte characters failed", URLEncoder.encode( fourByteCharacters, "UTF-8" ), fourByteEscaped ); Assert.assertEquals( "2-byte/4-byte mix failed", URLEncoder.encode( twoByteCharacters + fourByteCharacters, "UTF-8" ), twoByteEscaped + fourByteEscaped ); Assert.assertEquals( "1-byte/2-byte mix failed", URLEncoder.encode( oneByteCharacters + twoByteCharacters, "UTF-8" ), oneByteCharacters + twoByteEscaped ); Assert.assertEquals( "1-4 byte mix failed", URLEncoder.encode( oneByteCharacters + twoByteCharacters + fourByteCharacters, "UTF-8" ), oneByteCharacters + twoByteEscaped + fourByteEscaped ); } /** * Test creating one empty object. No metadata, no content. */ @Test public void testCreateEmptyObject() throws Exception { ObjectId id = this.api.createObject( null, null ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "", content ); } /** * Test creating one empty object on a path. No metadata, no content. */ @Test public void testCreateEmptyObjectOnPath() throws Exception { ObjectPath op = new ObjectPath( "/" + rand8char() ); ObjectId id = this.api.createObject( op, null, null ); cleanup.add( op ); l4j.debug( "Path: " + op + " ID: " + id ); Assert.assertNotNull( id ); // Read back the content String content = new String( this.api.readObject( op, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "", content ); content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong when reading by id", "", content ); } /** * Tests using some extended characters when creating on a path. This particular test * uses one cryllic, one accented, and one japanese character. */ @Test public void testUnicodePath() throws Exception { String dirName = rand8char(); ObjectPath path = new ObjectPath( "/" + dirName + "/бöシ.txt" ); ObjectId id = this.api.createObject( path, null, null ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); ObjectPath parent = new ObjectPath( "/" + dirName + "/" ); ListDirectoryResponse response = this.api.listDirectory( new ListDirectoryRequest().path( parent ) ); boolean found = false; for ( DirectoryEntry ent : response.getEntries() ) { if ( new ObjectPath( parent, ent.getFilename() ).equals( path ) ) { found = true; } } Assert.assertTrue( "Did not find unicode file in dir", found ); // Check read this.api.readObject( path, null, byte[].class ); } /** * Tests using some extra characters that might break URIs */ @Test public void testExtraPath() throws Exception { ObjectPath path = new ObjectPath( "/" + rand8char() + "/a+=- _!#$%^&*(),.z.txt" ); //ObjectPath path = new ObjectPath("/zimbramailbox/c8b4/511a-63c4-4ac9-8ff7+1c578de044be/stage/3r0sFrgUgL2ApCSkl3pobSX9D+k-1"); byte[] data = "Hello World".getBytes( "UTF-8" ); InputStream in = new ByteArrayInputStream( data ); CreateObjectRequest request = new CreateObjectRequest().identifier( path ).content( in ) .contentLength( data.length ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); } @Test public void testUtf8Path() throws Exception { String oneByteCharacters = "Hello! ,"; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols String crazyName = oneByteCharacters + twoByteCharacters + fourByteCharacters; byte[] content = "Crazy name creation test.".getBytes( "UTF-8" ); ObjectPath parent = createTestDir( "Utf8Path" ); ObjectPath path = new ObjectPath( parent, crazyName ); // create crazy-name object this.api.createObject( path, content, "text/plain" ); cleanup.add( path ); // verify name in directory list boolean found = false; ListDirectoryRequest request = new ListDirectoryRequest().path( parent ); for ( DirectoryEntry entry : this.api.listDirectory( request ).getEntries() ) { if ( new ObjectPath( parent, entry.getFilename() ).equals( path ) ) { found = true; break; } } Assert.assertTrue( "crazyName not found in directory listing", found ); // verify content Assert.assertTrue( "content does not match", Arrays.equals( content, this.api.readObject( path, null, byte[].class ) ) ); } @Test public void testUtf8Content() throws Exception { String oneByteCharacters = "Hello! ,"; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols byte[] content = (oneByteCharacters + twoByteCharacters + fourByteCharacters).getBytes( "UTF-8" ); // create object with multi-byte UTF-8 content ObjectId oid = api.createObject( content, "text/plain" ); cleanup.add( oid ); byte[] readContent = this.api.readObject( oid, null, byte[].class ); // verify content Assert.assertTrue( "content does not match", Arrays.equals( content, readContent ) ); } /** * Test creating an object with content but without metadata */ @Test public void testCreateObjectWithContent() throws Exception { ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); } @Test public void testCreateObjectWithSegment() throws Exception { byte[] content = "hello".getBytes( "UTF-8" ); ObjectId id = api.createObject( new BufferSegment( content, 0, content.length ), null ); cleanup.add( id ); // Read back the content String result = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", result ); } @Test public void testCreateObjectWithContentStream() throws Exception { InputStream in = new ByteArrayInputStream( "hello".getBytes( "UTF-8" ) ); CreateObjectRequest request = new CreateObjectRequest().content( in ).contentLength( 5 ) .contentType( "text/plain" ); ObjectId id = this.api.createObject( request ).getObjectId(); in.close(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); } @Test public void testCreateObjectWithContentStreamOnPath() throws Exception { ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" ); InputStream in = new ByteArrayInputStream( "hello".getBytes( "UTF-8" ) ); CreateObjectRequest request = new CreateObjectRequest(); request.identifier( op ).content( in ).contentLength( 5 ).contentType( "text/plain" ); ObjectId id = this.api.createObject( request ).getObjectId(); in.close(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); } /** * Test creating an object with metadata but no content. */ @Test public void testCreateObjectWithMetadataOnPath() { ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" ); CreateObjectRequest request = new CreateObjectRequest().identifier( op ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); //this.esu.updateObject( op, null, mlist, null, null, null ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( op ); // Read and validate the metadata Map<String, Metadata> meta = this.api.getUserMetadata( op ); Assert.assertNotNull( "value of 'listable' missing", meta.get( "listable" ) ); Assert.assertNotNull( "value of 'listable2' missing", meta.get( "listable2" ) ); Assert.assertNotNull( "value of 'unlistable' missing", meta.get( "unlistable" ) ); Assert.assertNotNull( "value of 'unlistable2' missing", meta.get( "unlistable2" ) ); Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() ); Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.get( "listable2" ).getValue() ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() ); Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", meta.get( "unlistable2" ).getValue() ); // Check listable flags Assert.assertEquals( "'listable' is not listable", true, meta.get( "listable" ).isListable() ); Assert.assertEquals( "'listable2' is not listable", true, meta.get( "listable2" ).isListable() ); Assert.assertEquals( "'unlistable' is listable", false, meta.get( "unlistable" ).isListable() ); Assert.assertEquals( "'unlistable2' is listable", false, meta.get( "unlistable2" ).isListable() ); } /** * Test creating an object with metadata but no content. */ @Test public void testCreateObjectWithMetadata() { CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); Metadata listable3 = new Metadata( "listable3", null, true ); Metadata quotes = new Metadata( "ST_modalities", "\\US\\", false ); //Metadata withCommas = new Metadata( "withcommas", "I, Robot", false ); //Metadata withEquals = new Metadata( "withequals", "name=value", false ); request.userMetadata( listable, unlistable, listable2, unlistable2, listable3, quotes ); //request.addUserMetadata( withCommas ); //request.addUserMetadata( withEquals ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read and validate the metadata Map<String, Metadata> meta = this.api.getUserMetadata( id ); Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() ); Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.get( "listable2" ).getValue() ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() ); Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", meta.get( "unlistable2" ).getValue() ); Assert.assertNotNull( "listable3 missing", meta.get( "listable3" ) ); Assert.assertTrue( "Value of listable3 should be empty", meta.get( "listable3" ).getValue() == null || meta.get( "listable3" ).getValue().length() == 0 ); //Assert.assertEquals( "Value of withcommas wrong", "I, Robot", meta.get( "withcommas" ).getValue() ); //Assert.assertEquals( "Value of withequals wrong", "name=value", meta.get( "withequals" ).getValue() ); // Check listable flags Assert.assertEquals( "'listable' is not listable", true, meta.get( "listable" ).isListable() ); Assert.assertEquals( "'listable2' is not listable", true, meta.get( "listable2" ).isListable() ); Assert.assertEquals( "'listable3' is not listable", true, meta.get( "listable3" ).isListable() ); Assert.assertEquals( "'unlistable' is listable", false, meta.get( "unlistable" ).isListable() ); Assert.assertEquals( "'unlistable2' is listable", false, meta.get( "unlistable2" ).isListable() ); } /** * Test creating an object with metadata but no content. */ @Test public void testMetadataNormalizeSpace() { CreateObjectRequest request = new CreateObjectRequest(); Metadata unlistable = new Metadata( "unlistable", "bar bar bar bar", false ); Metadata leadingSpacesOdd = new Metadata( "leadingodd", " spaces", false ); Metadata trailingSpacesOdd = new Metadata( "trailingodd", "spaces ", false ); Metadata leadingSpacesEven = new Metadata( "leadingeven", " SPACES", false ); Metadata trailingSpacesEven = new Metadata( "trailingeven", "spaces ", false ); request.userMetadata( unlistable, leadingSpacesOdd, trailingSpacesOdd, leadingSpacesEven, trailingSpacesEven ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read and validate the metadata Map<String, Metadata> meta = this.api.getUserMetadata( id ); Assert.assertEquals( "value of 'unlistable' wrong", "bar bar bar bar", meta.get( "unlistable" ).getValue() ); // Check listable flags Assert.assertEquals( "'unlistable' is listable", false, meta.get( "unlistable" ).isListable() ); } /** * Test reading an object's content */ @Test public void testReadObject() throws Exception { ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); // Read back only 2 bytes Range range = new Range( 1, 2 ); content = new String( this.api.readObject( id, range, byte[].class ), "UTF-8" ); Assert.assertEquals( "partial object content wrong", "el", content ); } @Test public void testResponseProperties() throws Exception { // Subtract a second since the HTTP dates only have 1s precision. Date now = new Date(); CreateObjectRequest request = new CreateObjectRequest().content( "hello".getBytes( "UTF-8" ) ) .contentType( "text/plain" ); CreateObjectResponse response = this.api.createObject( request ); Assert.assertNotNull( "null ID returned", response.getObjectId() ); Assert.assertEquals( "location wrong", "/rest/objects/" + response.getObjectId(), response.getLocation() ); cleanup.add( response.getObjectId() ); // Read back the content ReadObjectResponse<String> readResponse = api.readObject( new ReadObjectRequest().identifier( response.getObjectId() ), String.class ); Assert.assertEquals( "object content wrong", "hello", readResponse.getObject() ); Assert.assertEquals( "HTTP status wrong", 200, readResponse.getHttpStatus() ); Assert.assertEquals( "HTTP message wrong", "OK", readResponse.getHttpMessage() ); Assert.assertFalse( "HTTP headers empty", readResponse.getHeaders().isEmpty() ); Assert.assertTrue( "HTTP content-type wrong", readResponse.getContentType().matches( "text/plain(; charset=UTF-8)?" ) ); Assert.assertEquals( "HTTP content-length wrong", 5, readResponse.getContentLength() ); Assert.assertTrue( "HTTP response date wrong", Math.abs( response.getDate().getTime() - now.getTime() ) < (1000 * 60 * 5) ); // apparently last-modified isn't included in GET requests // Assert.assertTrue( "HTTP last modified date wrong", readResponse.getLastModified().after( now ) ); } /** * Test reading an ACL back */ @Test public void testReadAcl() { // Create an object with an ACL Acl acl = new Acl(); acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL ); acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ ); ObjectId id = this.api.createObject( new CreateObjectRequest().acl( acl ) ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the ACL and make sure it matches Acl newacl = this.api.getAcl( id ); l4j.info( "Comparing " + newacl + " with " + acl ); Assert.assertEquals( "ACLs don't match", acl, newacl ); } /** * Inside an ACL, you use the UID only, not SubtenantID/UID */ private String stripUid( String uid ) { int slash = uid.indexOf( '/' ); if ( slash != -1 ) { return uid.substring( slash + 1 ); } else { return uid; } } @Test public void testReadAclByPath() { ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" ); // Create an object with an ACL Acl acl = new Acl(); acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL ); acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ ); ObjectId id = this.api.createObject( new CreateObjectRequest().identifier( op ).acl( acl ) ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( op ); // Read back the ACL and make sure it matches Acl newacl = this.api.getAcl( op ); l4j.info( "Comparing " + newacl + " with " + acl ); Assert.assertEquals( "ACLs don't match", acl, newacl ); } /** * Test reading back user metadata */ @Test public void testGetUserMetadata() { // Create an object with user metadata CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read only part of the metadata Map<String, Metadata> meta = this.api.getUserMetadata( id, "listable", "unlistable" ); Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() ); Assert.assertNull( "value of 'listable2' should not have been returned", meta.get( "listable2" ) ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() ); Assert.assertNull( "value of 'unlistable2' should not have been returned", meta.get( "unlistable2" ) ); } /** * Test deleting user metadata */ @Test public void testDeleteUserMetadata() { // Create an object with metadata CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Delete a couple of the metadata entries this.api.deleteUserMetadata( id, "listable2", "unlistable2" ); // Read back the metadata for the object and ensure the deleted // entries don't exist Map<String, Metadata> meta = this.api.getUserMetadata( id ); Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() ); Assert.assertNull( "value of 'listable2' should not have been returned", meta.get( "listable2" ) ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() ); Assert.assertNull( "value of 'unlistable2' should not have been returned", meta.get( "unlistable2" ) ); } /** * Test creating object versions */ @Test public void testVersionObject() throws Exception { Assume.assumeFalse(isVipr); // Create an object String content = "Version Test"; CreateObjectRequest request = new CreateObjectRequest().content( content ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Version the object ObjectId vid = this.api.createVersion( id ); cleanup.add( vid ); Assert.assertNotNull( "null version ID returned", vid ); Assert.assertFalse( "Version ID shoudn't be same as original ID", id.equals( vid ) ); // Fetch the version and read its data Assert.assertEquals( "Version content wrong", content, this.api.readObject( vid, null, String.class ) ); Map<String, Metadata> meta = this.api.getUserMetadata( vid ); Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() ); Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.get( "listable2" ).getValue() ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() ); Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", meta.get( "unlistable2" ).getValue() ); } /** * Test listing the versions of an object */ @Test public void testListVersions() { Assume.assumeFalse(isVipr); // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); Assert.assertNotNull( "null ID returned", id ); // Version the object ObjectId vid1 = this.api.createVersion( id ); cleanup.add( vid1 ); Assert.assertNotNull( "null version ID returned", vid1 ); ObjectId vid2 = this.api.createVersion( id ); cleanup.add( vid2 ); Assert.assertNotNull( "null version ID returned", vid2 ); // List the versions and ensure their IDs are correct ListVersionsResponse response = this.api.listVersions( new ListVersionsRequest().objectId( id ) ); List<ObjectVersion> versions = response.getVersions(); Assert.assertEquals( "Wrong number of versions returned", 2, versions.size() ); Assert.assertTrue( "Version number less than zero", versions.get( 0 ).getVersionNumber() >= 0 ); Assert.assertNotNull( "Version itime is null", versions.get( 0 ).getItime() ); Assert.assertTrue( "Version number less than zero", versions.get( 1 ).getVersionNumber() >= 0 ); Assert.assertNotNull( "Version itime is null", versions.get( 1 ).getItime() ); List<ObjectId> versionIds = response.getVersionIds(); Assert.assertEquals( "Wrong number of versions returned", 2, versionIds.size() ); Assert.assertTrue( "version 1 not found in version list", versionIds.contains( vid1 ) ); Assert.assertTrue( "version 2 not found in version list", versionIds.contains( vid2 ) ); } /** * Test listing the versions of an object */ @Test public void testListVersionsLong() { Assume.assumeFalse(isVipr); // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); Assert.assertNotNull( "null ID returned", id ); // Version the object ObjectId vid1 = this.api.createVersion( id ); ObjectVersion v1 = new ObjectVersion( 0, vid1, null ); cleanup.add( vid1 ); Assert.assertNotNull( "null version ID returned", vid1 ); ObjectId vid2 = this.api.createVersion( id ); cleanup.add( vid2 ); ObjectVersion v2 = new ObjectVersion( 1, vid2, null ); Assert.assertNotNull( "null version ID returned", vid2 ); // List the versions and ensure their IDs are correct ListVersionsRequest vRequest = new ListVersionsRequest(); vRequest.objectId( id ).setLimit( 1 ); List<ObjectVersion> versions = new ArrayList<ObjectVersion>(); do { ListVersionsResponse response = this.api.listVersions( vRequest ); if ( response.getVersions() != null ) versions.addAll( response.getVersions() ); } while ( vRequest.getToken() != null ); Assert.assertEquals( "Wrong number of versions returned", 2, versions.size() ); Assert.assertTrue( "version 1 not found in version list", versions.contains( v1 ) ); Assert.assertTrue( "version 2 not found in version list", versions.contains( v2 ) ); for ( ObjectVersion v : versions ) { Assert.assertNotNull( "oid null in version", v.getVersionId() ); Assert.assertTrue( "Invalid version number in version", v.getVersionNumber() > -1 ); Assert.assertNotNull( "itime null in version", v.getItime() ); } } /** * Test listing the versions of an object */ @Test public void testDeleteVersion() { Assume.assumeFalse(isVipr); // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); Assert.assertNotNull( "null ID returned", id ); // Version the object ObjectId vid1 = this.api.createVersion( id ); Assert.assertNotNull( "null version ID returned", vid1 ); ObjectId vid2 = this.api.createVersion( id ); cleanup.add( vid2 ); Assert.assertNotNull( "null version ID returned", vid2 ); // List the versions and ensure their IDs are correct List<ObjectId> versions = this.api.listVersions( new ListVersionsRequest().objectId( id ) ).getVersionIds(); Assert.assertEquals( "Wrong number of versions returned", 2, versions.size() ); Assert.assertTrue( "version 1 not found in version list", versions.contains( vid1 ) ); Assert.assertTrue( "version 2 not found in version list", versions.contains( vid2 ) ); // Delete a version this.api.deleteVersion( vid1 ); versions = this.api.listVersions( new ListVersionsRequest().objectId( id ) ).getVersionIds(); Assert.assertEquals( "Wrong number of versions returned", 1, versions.size() ); Assert.assertFalse( "version 1 found in version list", versions.contains( vid1 ) ); Assert.assertTrue( "version 2 not found in version list", versions.contains( vid2 ) ); } @Test public void testRestoreVersion() throws IOException { Assume.assumeFalse(isVipr); ObjectId id = this.api.createObject( "Base Version Content".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Version the object ObjectId vId = this.api.createVersion( id ); // Update the object content this.api.updateObject( id, "Child Version Content -- You should never see me".getBytes( "UTF-8" ) ); // Restore the original version this.api.restoreVersion( id, vId ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "Base Version Content", content ); } /** * Test listing the system metadata on an object */ @Test public void testGetSystemMetadata() { // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read only part of the metadata Map<String, Metadata> meta = this.api.getSystemMetadata( id, "atime", "ctime" ); Assert.assertNotNull( "value of 'atime' missing", meta.get( "atime" ) ); Assert.assertNull( "value of 'mtime' should not have been returned", meta.get( "mtime" ) ); Assert.assertNotNull( "value of 'ctime' missing", meta.get( "ctime" ) ); Assert.assertNull( "value of 'gid' should not have been returned", meta.get( "gid" ) ); Assert.assertNull( "value of 'listable' should not have been returned", meta.get( "listable" ) ); } @Test public void testObjectExists() { ObjectId oid = api.createObject("Hello exists!", "text/plain"); Assert.assertTrue("object exists!", api.objectExists(oid)); api.delete(oid); Assert.assertFalse("object does not exist!", api.objectExists(oid)); } /** * Test listing objects by a tag that doesn't exist */ @Test public void testListObjectsNoExist() { ListObjectsRequest request = new ListObjectsRequest().metadataName( "this_tag_should_not_exist" ); List<ObjectEntry> objects = this.api.listObjects( request ).getEntries(); Assert.assertNotNull( "object list should be not null", objects ); Assert.assertEquals( "No objects should be returned", 0, objects.size() ); } /** * Test listing objects by a tag */ @Test public void testListObjects() { // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // List the objects. Make sure the one we created is in the list ListObjectsRequest lRequest = new ListObjectsRequest().metadataName( "listable" ); List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries(); ObjectEntry toFind = new ObjectEntry(); toFind.setObjectId( id ); Assert.assertTrue( "No objects returned", objects.size() > 0 ); Assert.assertTrue( "object not found in list", objects.contains( toFind ) ); } /** * Test listing objects by a tag */ @Test public void testListObjectsWithMetadata() { // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // List the objects. Make sure the one we created is in the list ListObjectsRequest lRequest = new ListObjectsRequest().metadataName( "listable" ).includeMetadata( true ); List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries(); Assert.assertTrue( "No objects returned", objects.size() > 0 ); // Find the item. boolean found = false; for ( ObjectEntry or : objects ) { if ( or.getObjectId().equals( id ) ) { found = true; // check metadata Assert.assertEquals( "Wrong value on metadata", or.getUserMetadataMap().get( "listable" ).getValue(), "foo" ); } } Assert.assertTrue( "object not found in list", found ); } /** * Test listing objects by a tag, with only some of the metadata */ @Test public void testListObjectsWithSomeMetadata() { // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // List the objects. Make sure the one we created is in the list ListObjectsRequest lRequest = new ListObjectsRequest(); lRequest.metadataName( "listable" ).includeMetadata( true ) .userMetadataNames( "listable" ); List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries(); Assert.assertTrue( "No objects returned", objects.size() > 0 ); // Find the item. boolean found = false; for ( ObjectEntry or : objects ) { if ( or.getObjectId().equals( id ) ) { found = true; // check metadata Assert.assertEquals( "Wrong value on metadata", or.getUserMetadataMap().get( "listable" ).getValue(), "foo" ); // Other metadata should not be present Assert.assertNull( "unlistable should be missing", or.getUserMetadataMap().get( "unlistable" ) ); } } Assert.assertTrue( "object not found in list", found ); } /** * Test listing objects by a tag, paging the results */ @Test public void testListObjectsPaged() { // Create two objects. CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); request.userMetadata( listable ); ObjectId id1 = this.api.createObject( request ).getObjectId(); ObjectId id2 = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id1 ); Assert.assertNotNull( "null ID returned", id2 ); cleanup.add( id1 ); cleanup.add( id2 ); // List the objects. Make sure the one we created is in the list ListObjectsRequest lRequest = new ListObjectsRequest().metadataName( "listable" ); lRequest.setIncludeMetadata( true ); lRequest.setLimit( 1 ); List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries(); Assert.assertTrue( "No objects returned", objects.size() > 0 ); Assert.assertNotNull( "Token should be present", lRequest.getToken() ); l4j.debug( "listObjectsPaged, Token: " + lRequest.getToken() ); while ( lRequest.getToken() != null ) { // Subsequent pages objects.addAll( this.api.listObjects( lRequest ).getEntries() ); l4j.debug( "listObjectsPaged, Token: " + lRequest.getToken() ); } // Ensure our IDs exist ObjectEntry toFind1 = new ObjectEntry(), toFind2 = new ObjectEntry(); toFind1.setObjectId( id1 ); toFind2.setObjectId( id2 ); Assert.assertTrue( "First object not found", objects.contains( toFind1 ) ); Assert.assertTrue( "Second object not found", objects.contains( toFind2 ) ); } /** * Test fetching listable tags */ @Test public void testGetListableTags() { // Create an object ObjectId id = this.api.createObject( null, null ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); UpdateObjectRequest request = new UpdateObjectRequest().identifier( id ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); this.api.updateObject( request ); // List tags. Ensure our object's tags are in the list. Set<String> tags = this.api.listMetadata( null ); Assert.assertTrue( "listable tag not returned", tags.contains( "listable" ) ); Assert.assertTrue( "list/able/2 root tag not returned", tags.contains( "list" ) ); Assert.assertFalse( "list/able/not tag returned", tags.contains( "list/able/not" ) ); // List child tags tags = this.api.listMetadata( "list/able" ); Assert.assertFalse( "non-child returned", tags.contains( "listable" ) ); Assert.assertTrue( "list/able/2 tag not returned", tags.contains( "2" ) ); Assert.assertFalse( "list/able/not tag returned", tags.contains( "not" ) ); } /** * Test listing the user metadata tags on an object */ @Test public void testListUserMetadataTags() { // Create an object CreateObjectRequest request = new CreateObjectRequest(); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // List tags Map<String, Boolean> metaNames = this.api.getUserMetadataNames( id ); Assert.assertTrue( "listable tag not returned", metaNames.containsKey( "listable" ) ); Assert.assertTrue( "list/able/2 tag not returned", metaNames.containsKey( "list/able/2" ) ); Assert.assertTrue( "unlistable tag not returned", metaNames.containsKey( "unlistable" ) ); Assert.assertTrue( "list/able/not tag not returned", metaNames.containsKey( "list/able/not" ) ); Assert.assertFalse( "unknown tag returned", metaNames.containsKey( "unknowntag" ) ); // Check listable flag Assert.assertEquals( "'listable' is not listable", true, metaNames.get( "listable" ) ); Assert.assertEquals( "'list/able/2' is not listable", true, metaNames.get( "list/able/2" ) ); Assert.assertEquals( "'unlistable' is listable", false, metaNames.get( "unlistable" ) ); Assert.assertEquals( "'list/able/not' is listable", false, metaNames.get( "list/able/not" ) ); } /** * Tests updating an object's metadata */ @Test public void testUpdateObjectMetadata() throws Exception { // Create an object CreateObjectRequest request = new CreateObjectRequest().content( "hello".getBytes( "UTF-8" ) ); Metadata unlistable = new Metadata( "unlistable", "foo", false ); request.userMetadata( unlistable ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Update the metadata unlistable.setValue( "bar" ); this.api.setUserMetadata( id, request.getUserMetadata().toArray( new Metadata[request.getUserMetadata().size()] ) ); // Re-read the metadata Map<String, Metadata> meta = this.api.getUserMetadata( id ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() ); // Check that content was not modified String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); } @Test public void testUpdateObjectAcl() throws Exception { // Create an object with an ACL Acl acl = new Acl(); acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL ); acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ ); ObjectId id = this.api.createObject( new CreateObjectRequest().acl( acl ) ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the ACL and make sure it matches Acl newacl = this.api.getAcl( id ); l4j.info( "Comparing " + newacl + " with " + acl ); Assert.assertEquals( "ACLs don't match", acl, newacl ); // Change the ACL and update the object. acl.removeGroupGrant( Acl.GROUP_OTHER ); acl.addGroupGrant( Acl.GROUP_OTHER, Permission.NONE ); this.api.setAcl( id, acl ); // Read the ACL back and check it newacl = this.api.getAcl( id ); l4j.info( "Comparing " + newacl + " with " + acl ); Assert.assertEquals( "ACLs don't match", acl, newacl ); } /** * Tests updating an object's contents */ @Test public void testUpdateObjectContent() throws Exception { // Create an object ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Update part of the content Range range = new Range( 1, 1 ); this.api.updateObject( id, "u".getBytes( "UTF-8" ), range ); // Read back the content and check it String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hullo", content ); } @Test public void testUpdateObjectContentStream() throws Exception { // Create an object ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Update part of the content InputStream in = new ByteArrayInputStream( "u".getBytes( "UTF-8" ) ); UpdateObjectRequest request = new UpdateObjectRequest().identifier( id ); request.range( new Range( 1, 1 ) ).content( in ); request.setContentLength( 1 ); this.api.updateObject( request ); in.close(); // Read back the content and check it String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hullo", content ); } /** * Test replacing an object's entire contents */ @Test public void testReplaceObjectContent() throws Exception { // Create an object ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Update all of the content this.api.updateObject( id, "bonjour".getBytes( "UTF-8" ) ); // Read back the content and check it String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "bonjour", content ); } @Test public void testListDirectory() throws Exception { String dir = rand8char(); String file = rand8char(); String dir2 = rand8char(); ObjectPath dirPath = new ObjectPath( "/" + dir + "/" ); ObjectPath op = new ObjectPath( "/" + dir + "/" + file ); ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" ); ObjectId dirId = this.api.createDirectory( dirPath ); ObjectId id = this.api.createObject( op, null, null ); this.api.createDirectory( dirPath2 ); cleanup.add( op ); cleanup.add( dirPath2 ); cleanup.add( dirPath ); l4j.debug( "Path: " + op + " ID: " + id ); Assert.assertNotNull( id ); Assert.assertNotNull( dirId ); // Read back the content String content = new String( this.api.readObject( op, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "", content ); content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong when reading by id", "", content ); // List the parent path List<DirectoryEntry> dirList = api.listDirectory( new ListDirectoryRequest().path( dirPath ) ).getEntries(); l4j.debug( "Dir content: " + content ); Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) ); Assert.assertTrue( "subdirectory not found in directory", directoryContains( dirList, dirPath2.getFilename() ) ); } @Test public void testListDirectoryPaged() throws Exception { String dir = rand8char(); String file = rand8char(); String dir2 = rand8char(); ObjectPath dirPath = new ObjectPath( "/" + dir + "/" ); ObjectPath op = new ObjectPath( "/" + dir + "/" + file ); ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" ); ObjectId dirId = this.api.createDirectory( dirPath ); ObjectId id = this.api.createObject( op, null, null ); this.api.createDirectory( dirPath2 ); cleanup.add( op ); cleanup.add( dirPath2 ); cleanup.add( dirPath ); l4j.debug( "Path: " + op + " ID: " + id ); Assert.assertNotNull( id ); Assert.assertNotNull( dirId ); // List the parent path ListDirectoryRequest request = new ListDirectoryRequest().path( dirPath ); request.setLimit( 1 ); List<DirectoryEntry> dirList = api.listDirectory( request ).getEntries(); Assert.assertNotNull( "Token should have been returned", request.getToken() ); l4j.debug( "listDirectoryPaged, token: " + request.getToken() ); while ( request.getToken() != null ) { dirList.addAll( api.listDirectory( request ).getEntries() ); } Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) ); Assert.assertTrue( "subdirectory not found in directory", directoryContains( dirList, dirPath2.getFilename() ) ); } @Test public void testListDirectoryWithMetadata() throws Exception { String dir = rand8char(); String file = rand8char(); String dir2 = rand8char(); ObjectPath dirPath = new ObjectPath( "/" + dir + "/" ); ObjectPath op = new ObjectPath( "/" + dir + "/" + file ); ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" ); CreateObjectRequest request = new CreateObjectRequest().identifier( op ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId dirId = this.api.createDirectory( dirPath ); ObjectId id = this.api.createObject( request ).getObjectId(); this.api.createDirectory( dirPath2 ); cleanup.add( op ); cleanup.add( dirPath2 ); cleanup.add( dirPath ); l4j.debug( "Path: " + op + " ID: " + id ); Assert.assertNotNull( id ); Assert.assertNotNull( dirId ); // List the parent path ListDirectoryRequest lRequest = new ListDirectoryRequest().path( dirPath ).includeMetadata( true ); List<DirectoryEntry> dirList = api.listDirectory( lRequest ).getEntries(); Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) ); Assert.assertTrue( "subdirectory not found in directory", directoryContains( dirList, dirPath2.getFilename() ) ); for ( DirectoryEntry de : dirList ) { if ( new ObjectPath( dirPath, de.getFilename() ).equals( op ) ) { // Check the metadata Assert.assertNotNull("missing metadata 'listable'", de.getUserMetadataMap().get( "listable" )); Assert.assertEquals( "Wrong value on metadata", de.getUserMetadataMap().get( "listable" ).getValue(), "foo" ); } } Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) ); Assert.assertTrue( "subdirectory not found in directory", directoryContains( dirList, dirPath2.getFilename() ) ); } @Test public void testListDirectoryWithSomeMetadata() throws Exception { String dir = rand8char(); String file = rand8char(); String dir2 = rand8char(); ObjectPath dirPath = new ObjectPath( "/" + dir + "/" ); ObjectPath op = new ObjectPath( "/" + dir + "/" + file ); ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" ); CreateObjectRequest request = new CreateObjectRequest().identifier( op ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false ); request.userMetadata( listable, unlistable, listable2, unlistable2 ); ObjectId dirId = this.api.createDirectory( dirPath ); ObjectId id = this.api.createObject( request ).getObjectId(); this.api.createDirectory( dirPath2 ); cleanup.add( op ); cleanup.add( dirPath2 ); cleanup.add( dirPath ); l4j.debug( "Path: " + op + " ID: " + id ); Assert.assertNotNull( id ); Assert.assertNotNull( dirId ); // List the parent path ListDirectoryRequest lRequest = new ListDirectoryRequest().path( dirPath ).includeMetadata( true ); lRequest.userMetadataNames( "listable" ); List<DirectoryEntry> dirList = api.listDirectory( lRequest ).getEntries(); Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) ); Assert.assertTrue( "subdirectory not found in directory", directoryContains( dirList, dirPath2.getFilename() ) ); for ( DirectoryEntry de : dirList ) { if ( new ObjectPath( dirPath, de.getFilename() ).equals( op ) ) { // Check the metadata Assert.assertNotNull("Missing metadata 'listable'", de.getUserMetadataMap().get( "listable" )); Assert.assertEquals( "Wrong value on metadata", de.getUserMetadataMap().get( "listable" ).getValue(), "foo" ); // Other metadata should not be present Assert.assertNull( "unlistable should be missing", de.getUserMetadataMap().get( "unlistable" ) ); } } Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) ); Assert.assertTrue( "subdirectory not found in directory", directoryContains( dirList, dirPath2.getFilename() ) ); } private boolean directoryContains( List<DirectoryEntry> dir, String filename ) { for ( DirectoryEntry de : dir ) { if ( de.getFilename().equals( filename ) ) { return true; } } return false; } /** * This method tests various legal and illegal pathnames * * @throws Exception */ @Test public void testPathNaming() throws Exception { ObjectPath path = new ObjectPath( "/some/file" ); Assert.assertFalse( "File should not be directory", path.isDirectory() ); path = new ObjectPath( "/some/file.txt" ); Assert.assertFalse( "File should not be directory", path.isDirectory() ); ObjectPath path2 = new ObjectPath( "/some/file.txt" ); Assert.assertEquals( "Equal paths should be equal", path, path2 ); path = new ObjectPath( "/some/file/with/long.path/extra.stuff.here.zip" ); Assert.assertFalse( "File should not be directory", path.isDirectory() ); path = new ObjectPath( "/" ); Assert.assertTrue( "Directory should be directory", path.isDirectory() ); path = new ObjectPath( "/long/path/with/lots/of/elements/" ); Assert.assertTrue( "Directory should be directory", path.isDirectory() ); } /** * Tests dot directories (you should be able to create them even though they break the URL specification.) * * @throws Exception */ @Test public void testDotDirectories() throws Exception { ObjectPath parentPath = createTestDir("DotDirectories"); ObjectPath dotPath = new ObjectPath( parentPath, "./" ); ObjectPath dotdotPath = new ObjectPath( parentPath, "../" ); String filename = rand8char(); byte[] content = "Hello World!".getBytes("UTF-8"); // test single dot path (./) ObjectId dirId = this.api.createDirectory( dotPath ); Assert.assertNotNull( "null ID returned on dot path creation", dirId ); ObjectId fileId = this.api.createObject( new ObjectPath( dotPath, filename ), content, "text/plain" ); cleanup.add( fileId ); cleanup.add( dirId ); // make sure we only see one file (the "." path is its own directory and not a synonym for the current directory) List<DirectoryEntry> entries = this.api .listDirectory( new ListDirectoryRequest().path( dotPath ) ).getEntries(); Assert.assertEquals( "dot path listing was not 1", entries.size(), 1 ); Assert.assertEquals( "dot path listing did not contain test file", entries.get( 0 ).getFilename(), filename ); // test double dot path (../) dirId = this.api.createDirectory( dotdotPath ); Assert.assertNotNull( "null ID returned on dotdot path creation", dirId ); fileId = this.api.createObject( new ObjectPath( dotdotPath, filename ), content, "text/plain" ); cleanup.add( fileId ); cleanup.add( dirId ); // make sure we only see one file (the ".." path is its own directory and not a synonym for the parent directory) entries = this.api .listDirectory( new ListDirectoryRequest().path( dotdotPath ) ).getEntries(); Assert.assertEquals( "dotdot path listing was not 1", entries.size(), 1 ); Assert.assertEquals("dotdot path listing did not contain test file", entries.get(0).getFilename(), filename); } /** * Tests the 'get all metadata' call using a path * * @throws Exception */ @Test public void testGetAllMetadataByPath() throws Exception { ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" ); String mimeType = "test/mimetype"; // Create an object with an ACL Acl acl = new Acl(); acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL ); acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ ); CreateObjectRequest request = new CreateObjectRequest().identifier( op ).acl( acl ); request.content( "test".getBytes( "UTF-8" ) ).contentType( mimeType ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( op ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); this.api.updateObject( new UpdateObjectRequest().identifier( op ) .userMetadata( listable, unlistable, listable2, unlistable2 ) .contentType( mimeType ) ); // Read it back with HEAD call ObjectMetadata om = this.api.getObjectMetadata( op ); Assert.assertNotNull( "value of 'listable' missing", om.getMetadata().get( "listable" ) ); Assert.assertNotNull( "value of 'unlistable' missing", om.getMetadata().get( "unlistable" ) ); Assert.assertNotNull( "value of 'atime' missing", om.getMetadata().get( "atime" ) ); Assert.assertNotNull( "value of 'ctime' missing", om.getMetadata().get( "ctime" ) ); Assert.assertEquals( "value of 'listable' wrong", "foo", om.getMetadata().get( "listable" ).getValue() ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", om.getMetadata().get( "unlistable" ).getValue() ); Assert.assertEquals( "Mimetype incorrect", mimeType, om.getContentType() ); // Check the ACL // not checking this by path because an extra groupid is added // during the create calls by path. //Assert.assertEquals( "ACLs don't match", acl, om.getAcl() ); } @Test public void testGetAllMetadataById() throws Exception { // Create an object with an ACL CreateObjectRequest request = new CreateObjectRequest(); Acl acl = new Acl(); acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL ); acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); String mimeType = "test/mimetype"; request.acl( acl ).userMetadata( listable, unlistable, listable2, unlistable2 ); request.content( "test".getBytes( "UTF-8" ) ).contentType( mimeType ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read it back with HEAD call ObjectMetadata om = this.api.getObjectMetadata( id ); Assert.assertNotNull( "value of 'listable' missing", om.getMetadata().get( "listable" ) ); Assert.assertNotNull( "value of 'unlistable' missing", om.getMetadata().get( "unlistable" ) ); Assert.assertNotNull( "value of 'atime' missing", om.getMetadata().get( "atime" ) ); Assert.assertNotNull( "value of 'ctime' missing", om.getMetadata().get( "ctime" ) ); Assert.assertEquals( "value of 'listable' wrong", "foo", om.getMetadata().get( "listable" ).getValue() ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", om.getMetadata().get( "unlistable" ).getValue() ); Assert.assertEquals( "Mimetype incorrect", mimeType, om.getContentType() ); // Check the ACL Assert.assertEquals( "ACLs don't match", acl, om.getAcl() ); } /** * Tests getting object replica information. */ @Test public void testGetObjectReplicaInfo() throws Exception { ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); Map<String, Metadata> meta = this.api.getUserMetadata( id, "user.maui.lso" ); Assert.assertNotNull( meta.get( "user.maui.lso" ) ); l4j.debug( "Replica info: " + meta.get( "user.maui.lso" ) ); } @Test public void testGetShareableUrl() throws Exception { Assume.assumeFalse(isVipr); // Create an object with content. String str = "Four score and twenty years ago"; ObjectId id = this.api.createObject( str.getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); Calendar c = Calendar.getInstance(); c.add( Calendar.HOUR, 4 ); Date expiration = c.getTime(); URL u = api.getShareableUrl( id, expiration ); l4j.debug( "Sharable URL: " + u ); InputStream stream = (InputStream) u.getContent(); BufferedReader br = new BufferedReader( new InputStreamReader( stream ) ); String content = br.readLine(); l4j.debug( "Content: " + content ); Assert.assertEquals( "URL does not contain proper content", str, content ); } @Test public void testGetShareableUrlWithPath() throws Exception { Assume.assumeFalse(isVipr); // Create an object with content. String str = "Four score and twenty years ago"; ObjectPath op = new ObjectPath( "/" + rand8char() + ".txt" ); ObjectId id = this.api.createObject( op, str.getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( op ); Calendar c = Calendar.getInstance(); c.add( Calendar.HOUR, 4 ); Date expiration = c.getTime(); URL u = api.getShareableUrl( op, expiration ); l4j.debug( "Sharable URL: " + u ); InputStream stream = (InputStream) u.getContent(); BufferedReader br = new BufferedReader( new InputStreamReader( stream ) ); String content = br.readLine(); l4j.debug( "Content: " + content ); Assert.assertEquals( "URL does not contain proper content", str, content ); } @Test public void testExpiredSharableUrl() throws Exception { Assume.assumeFalse(isVipr); // Create an object with content. String str = "Four score and twenty years ago"; ObjectId id = this.api.createObject( str.getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); Calendar c = Calendar.getInstance(); c.add( Calendar.HOUR, -4 ); Date expiration = c.getTime(); URL u = api.getShareableUrl( id, expiration ); l4j.debug( "Sharable URL: " + u ); try { InputStream stream = (InputStream) u.getContent(); BufferedReader br = new BufferedReader( new InputStreamReader( stream ) ); String content = br.readLine(); l4j.debug( "Content: " + content ); Assert.fail( "Request should have failed" ); } catch ( Exception e ) { l4j.debug( "Error (expected): " + e ); } } @Test public void testReadObjectStream() throws Exception { ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read back the content InputStream in = this.api.readObjectStream( id, null ).getObject(); BufferedReader br = new BufferedReader( new InputStreamReader( in, "UTF-8" ) ); String content = br.readLine(); br.close(); Assert.assertEquals( "object content wrong", "hello", content ); // Read back only 2 bytes Range range = new Range( 1, 2 ); in = this.api.readObjectStream( id, range ).getObject(); br = new BufferedReader( new InputStreamReader( in, "UTF-8" ) ); content = br.readLine(); br.close(); Assert.assertEquals( "partial object content wrong", "el", content ); } @Test public void testCreateChecksum() throws Exception { byte[] data = "hello".getBytes( "UTF-8" ); RunningChecksum ck = new RunningChecksum( ChecksumAlgorithm.SHA0 ); ck.update( data, 0, data.length ); CreateObjectRequest request = new CreateObjectRequest().content( data ).contentType( "text/plain" ); request.wsChecksum( ck ); CreateObjectResponse response = this.api.createObject( request ); cleanup.add( response.getObjectId() ); Assert.assertNotNull( "Null object ID returned", response.getObjectId() ); Assert.assertEquals( "Checksum doesn't match", ck, response.getWsChecksum() ); } /** * Note, to test read checksums, see comment in testReadChecksum * * @throws Exception */ @Test public void testUploadDownloadChecksum() throws Exception { // Create a byte array to test int totalSize = 10 * 1024 * 1024; // 10MB int chunkSize = 4 * 1024 * 1024; // 4MB byte[] testData = new byte[totalSize]; // 10MB for ( int i = 0; i < testData.length; i++ ) { testData[i] = (byte) (i % 0x93); } RunningChecksum sha0 = new RunningChecksum( ChecksumAlgorithm.SHA0 ); BufferSegment segment = new BufferSegment( testData, 0, chunkSize ); // upload in chunks sha0.update( segment ); l4j.debug( "Create checksum: " + sha0 ); CreateObjectRequest request = new CreateObjectRequest(); request.content( segment ).userMetadata( new Metadata( "policy", "erasure", false ) ).setWsChecksum( sha0 ); ObjectId id = api.createObject( request ).getObjectId(); cleanup.add( id ); while ( segment.getOffset() + segment.getSize() < totalSize ) { segment.setOffset( segment.getOffset() + chunkSize ); if ( segment.getOffset() + chunkSize > totalSize ) segment.setSize( totalSize - segment.getOffset() ); Range range = new Range( segment.getOffset(), segment.getOffset() + segment.getSize() - 1 ); sha0.update( segment.getBuffer(), segment.getOffset(), segment.getSize() ); l4j.debug( "Update checksum: " + sha0 ); api.updateObject( new UpdateObjectRequest().identifier( id ).range( range ) .content( segment ).wsChecksum( sha0 ) ); } // download in chunks totalSize = Integer.parseInt( api.getSystemMetadata( id ).get( "size" ).getValue() ); ByteArrayOutputStream out = new ByteArrayOutputStream(); int first = 0, last = chunkSize - 1; Range range = new Range( first, last ); ReadObjectResponse<byte[]> response; RunningChecksum readSha0 = new RunningChecksum( ChecksumAlgorithm.SHA0 ); do { response = api.readObject( new ReadObjectRequest().identifier( id ).ranges( range ), byte[].class ); readSha0.update( response.getObject(), 0, response.getObject().length ); out.write( response.getObject() ); first += chunkSize; last += chunkSize; if ( last >= totalSize ) last = totalSize - 1; range = new Range( first, last ); } while ( first < totalSize ); byte[] outData = out.toByteArray(); // verify checksum Assert.assertEquals( "Write checksum doesn't match read checksum", sha0, readSha0 ); Assert.assertEquals( "Read checksum doesn't match", readSha0, response.getWsChecksum() ); // Check the files Assert.assertEquals( "File lengths differ", testData.length, outData.length ); Assert.assertArrayEquals( "Data contents differ", testData, outData ); } @Ignore("TODO: Figure out why this fails") @Test public void testUtf8WhiteSpaceValues() throws Exception { String utf8String = "Hello ,\u0080 \r \u000B \t \n \t"; CreateObjectRequest request = new CreateObjectRequest(); request.userMetadata( new Metadata( "utf8Key", utf8String, false ) ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); // get the user metadata and make sure all UTF8 characters are accurate Map<String, Metadata> metaMap = this.api.getUserMetadata( id ); Assert.assertEquals( "UTF8 value does not match", utf8String, metaMap.get( "utf8Key" ).getValue() ); // test set metadata with UTF8 this.api.setUserMetadata( id, new Metadata( "newKey", utf8String + "2", false ) ); // verify set metadata call (also testing getAllMetadata) ObjectMetadata objMeta = this.api.getObjectMetadata( id ); metaMap = objMeta.getMetadata(); //Assert.assertEquals( "UTF8 key does not match", meta.getName(), whiteSpaceString + "2" ); //Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "newValue" ); Assert.assertEquals( "UTF8 value does not match", utf8String + "2", metaMap.get( "newKey" ).getValue() ); } @Test public void testUnicodeMetadata() throws Exception { CreateObjectRequest request = new CreateObjectRequest(); Metadata nbspValue = new Metadata( "nbspvalue", "Nobreak\u00A0Value", false ); Metadata nbspName = new Metadata( "Nobreak\u00A0Name", "regular text here", false ); Metadata cryllic = new Metadata( "cryllic", "спасибо", false ); l4j.debug( "NBSP Value: " + nbspValue ); l4j.debug( "NBSP Name: " + nbspName ); request.userMetadata( nbspValue, nbspName, cryllic ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read and validate the metadata Map<String, Metadata> meta = this.api.getUserMetadata( id ); l4j.debug( "Read Back:" ); l4j.debug( "NBSP Value: " + meta.get( "nbspvalue" ) ); l4j.debug( "NBSP Name: " + meta.get( "Nobreak\u00A0Name" ) ); Assert.assertEquals( "value of 'nobreakvalue' wrong", "Nobreak\u00A0Value", meta.get( "nbspvalue" ).getValue() ); Assert.assertEquals( "Value of cryllic wrong", "спасибо", meta.get( "cryllic" ).getValue() ); } @Test public void testUtf8Metadata() throws Exception { String oneByteCharacters = "Hello! "; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters; CreateObjectRequest request = new CreateObjectRequest(); request.userMetadata( new Metadata( "utf8Key", utf8String, false ), new Metadata( utf8String, "utf8Value", false ) ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); // list all tags and make sure the UTF8 tag is in the list Map<String, Boolean> tags = this.api.getUserMetadataNames( id ); Assert.assertTrue( "UTF8 key not found in tag list", tags.containsKey( utf8String ) ); // get the user metadata and make sure all UTF8 characters are accurate Map<String, Metadata> metaMap = this.api.getUserMetadata( id ); Metadata meta = metaMap.get( utf8String ); Assert.assertEquals( "UTF8 key does not match", meta.getName(), utf8String ); Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "utf8Value" ); Assert.assertEquals( "UTF8 value does not match", metaMap.get( "utf8Key" ).getValue(), utf8String ); // test set metadata with UTF8 this.api.setUserMetadata( id, new Metadata( "newKey", utf8String + "2", false ), new Metadata( utf8String + "2", "newValue", false ) ); // verify set metadata call (also testing getAllMetadata) ObjectMetadata objMeta = this.api.getObjectMetadata( id ); metaMap = objMeta.getMetadata(); meta = metaMap.get( utf8String + "2" ); Assert.assertEquals( "UTF8 key does not match", meta.getName(), utf8String + "2" ); Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "newValue" ); Assert.assertEquals( "UTF8 value does not match", metaMap.get( "newKey" ).getValue(), utf8String + "2" ); } @Test public void testUtf8MetadataFilter() throws Exception { String oneByteCharacters = "Hello! "; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters; CreateObjectRequest request = new CreateObjectRequest(); request.userMetadata( new Metadata( "utf8Key", utf8String, false ) ) .userMetadata( new Metadata( utf8String, "utf8Value", false ) ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); // apply a filter that includes the UTF8 tag Map<String, Metadata> metaMap = this.api.getUserMetadata( id, utf8String ); Assert.assertEquals( "UTF8 filter was not honored", metaMap.size(), 1 ); Assert.assertNotNull( "UTF8 key was not found in filtered results", metaMap.get( utf8String ) ); } @Test public void testUtf8DeleteMetadata() throws Exception { String oneByteCharacters = "Hello! "; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters; CreateObjectRequest request = new CreateObjectRequest(); request.userMetadata( new Metadata( "utf8Key", utf8String, false ) ) .userMetadata( new Metadata( utf8String, "utf8Value", false ) ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); // delete the UTF8 tag this.api.deleteUserMetadata( id, utf8String ); // verify delete was successful Map<String, Boolean> nameMap = this.api.getUserMetadataNames( id ); Assert.assertFalse( "UTF8 key was not deleted", nameMap.containsKey( utf8String ) ); } @Test public void testUtf8ListableMetadata() throws Exception { String oneByteCharacters = "Hello! "; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters; CreateObjectRequest request = new CreateObjectRequest(); request.userMetadata( new Metadata( utf8String, "utf8Value", true ) ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); Map<String, Metadata> metaMap = this.api.getUserMetadata( id ); Metadata meta = metaMap.get( utf8String ); Assert.assertEquals( "UTF8 key does not match", meta.getName(), utf8String ); Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "utf8Value" ); Assert.assertTrue( "UTF8 metadata is not listable", meta.isListable() ); // verify we can list the tag and see our object boolean found = false; for ( ObjectEntry result : this.api .listObjects( new ListObjectsRequest().metadataName( utf8String ) ).getEntries() ) { if ( result.getObjectId().equals( id ) ) { found = true; break; } } Assert.assertTrue( "UTF8 tag listing did not contain the correct object ID", found ); // verify we can list child tags of the UTF8 tag Set<String> tags = this.api.listMetadata( utf8String ); Assert.assertNotNull( "UTF8 child tag listing was null", tags ); } @Test public void testUtf8ListableTagWithComma() { String stringWithComma = "Hello, you!"; CreateObjectRequest request = new CreateObjectRequest(); request.userMetadata( new Metadata( stringWithComma, "value", true ) ); ObjectId id = this.api.createObject( request ).getObjectId(); cleanup.add( id ); Map<String, Metadata> metaMap = this.api.getUserMetadata( id ); Metadata meta = metaMap.get( stringWithComma ); Assert.assertEquals( "key does not match", meta.getName(), stringWithComma ); Assert.assertTrue( "metadata is not listable", meta.isListable() ); boolean found = false; for ( ObjectEntry result : this.api .listObjects( new ListObjectsRequest().metadataName( stringWithComma ) ).getEntries() ) { if ( result.getObjectId().equals( id ) ) { found = true; break; } } Assert.assertTrue( "listing did not contain the correct object ID", found ); } @Test public void testRename() throws Exception { ObjectPath op1 = new ObjectPath( "/" + rand8char() + ".tmp" ); ObjectPath op2 = new ObjectPath( "/" + rand8char() + ".tmp" ); ObjectId id = this.api.createObject( op1, "Four score and seven years ago".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Rename this.api.move( op1, op2, false ); // Read back the content String content = new String( this.api.readObject( op2, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "Four score and seven years ago", content ); } @Test public void testRenameOverwrite() throws Exception { ObjectPath op1 = new ObjectPath( "/" + rand8char() + ".tmp" ); ObjectPath op2 = new ObjectPath( "/" + rand8char() + ".tmp" ); ObjectId id = this.api.createObject( op1, "Four score and seven years ago".getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); ObjectId id2 = this.api.createObject( op2, "You should not see this".getBytes( "UTF-8" ), "text/plain" ); cleanup.add( id2 ); // Rename this.api.move( op1, op2, true ); // Wait for overwrite to complete Thread.sleep( 5000 ); // Read back the content String content = new String( this.api.readObject( op2, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "Four score and seven years ago", content ); } /** * Tests renaming a path to UTF-8 multi-byte characters. This is a separate test from create as the characters are * passed in the headers instead of the URL itself. * * @throws Exception */ @Test public void testUtf8Rename() throws Exception { ObjectPath parentDir = createTestDir("Utf8Rename"); String oneByteCharacters = "Hello! ,"; String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols ObjectPath normalName = new ObjectPath( parentDir, rand8char() + ".tmp"); String crazyName = oneByteCharacters + twoByteCharacters + fourByteCharacters; ObjectPath crazyPath = new ObjectPath( parentDir, crazyName ); byte[] content = "This is a really crazy name.".getBytes( "UTF-8" ); // normal name this.api.createObject( normalName, content, "text/plain" ); // crazy multi-byte character name this.api.move( normalName, crazyPath, true ); // Wait for overwrite to complete Thread.sleep( 5000 ); // verify name in directory list List<DirectoryEntry> entries = this.api.listDirectory(new ListDirectoryRequest().path(parentDir)).getEntries(); Assert.assertTrue( "crazyName not found in directory listing", directoryContains( entries, crazyName ) ); // Read back the content Assert.assertTrue( "object content wrong", Arrays.equals( content, this.api.readObject( crazyPath, null, byte[].class ) ) ); } @Test public void testPositiveChecksumValidation() throws Exception { byte[] data = "Hello Checksums!".getBytes("UTF-8"); RunningChecksum md5 = new RunningChecksum(ChecksumAlgorithm.MD5); RunningChecksum sha0 = new RunningChecksum(ChecksumAlgorithm.SHA0); RunningChecksum sha1 = new RunningChecksum(ChecksumAlgorithm.SHA1); md5.update(data, 0, data.length); sha0.update(data, 0, data.length); sha1.update(data, 0, data.length); CreateObjectRequest request = new CreateObjectRequest().content(data); ObjectId md5Id = api.createObject(request.wsChecksum(md5)).getObjectId(); ObjectId sha0Id = api.createObject(request.wsChecksum(sha0)).getObjectId(); ObjectId sha1Id = api.createObject(request.wsChecksum(sha1)).getObjectId(); cleanup.add(md5Id); cleanup.add(sha0Id); cleanup.add(sha1Id); Assert.assertEquals("MD5 checksum was not equal", md5, api.readObject(new ReadObjectRequest().identifier(md5Id), byte[].class).getWsChecksum()); Assert.assertEquals("SHA0 checksum was not equal", sha0, api.readObject(new ReadObjectRequest().identifier(sha0Id), byte[].class).getWsChecksum()); Assert.assertEquals("SHA1 checksum was not equal", sha1, api.readObject(new ReadObjectRequest().identifier(sha1Id), byte[].class).getWsChecksum()); // do a bunch of calls to make sure we don't try to validate api.getSystemMetadata(md5Id); api.getObjectMetadata(sha1Id); api.getObjectInfo(sha0Id); api.readObject(md5Id, new Range(1, 8), byte[].class); api.listVersions(new ListVersionsRequest().objectId(sha1Id)); api.getAcl(sha0Id); Assert.assertTrue("object stream is not a ChecksummedInputStream", api.readObjectStream(sha0Id, null).getObject() instanceof ChecksummedInputStream); // test update byte[] appendData = " and stuff!".getBytes("UTF-8"); md5.update(appendData, 0, appendData.length); UpdateObjectRequest uRequest = new UpdateObjectRequest().identifier(md5Id).content(appendData).wsChecksum(md5); uRequest.setRange(new Range(data.length, data.length + appendData.length - 1)); api.updateObject(uRequest); Assert.assertTrue("object stream is not a ChecksummedInputStream", api.readObjectStream(md5Id, null).getObject() instanceof ChecksummedInputStream); api.readObject(md5Id, byte[].class); } /** * Tests readback with checksum verification. In order to test this, create a policy * with erasure coding and then set a policy selector with "policy=erasure" to invoke * the erasure coding policy. * * @throws Exception */ @Test public void testReadChecksum() throws Exception { byte[] data = "hello".getBytes( "UTF-8" ); Metadata policy = new Metadata( "policy", "erasure", false ); RunningChecksum wsChecksum = new RunningChecksum( ChecksumAlgorithm.SHA0 ); wsChecksum.update( data, 0, data.length ); CreateObjectRequest request = new CreateObjectRequest().content( data ).contentType( "text/plain" ); request.userMetadata( policy ).wsChecksum( wsChecksum ); CreateObjectResponse response = this.api.createObject( request ); Assert.assertNotNull( "null ID returned", response.getObjectId() ); cleanup.add( response.getObjectId() ); Assert.assertNotNull( "null ID returned", response.getObjectId() ); Assert.assertEquals( "create checksums don't match", wsChecksum, response.getWsChecksum() ); // Read back the content ReadObjectRequest readRequest = new ReadObjectRequest().identifier( response.getObjectId() ); ReadObjectResponse<byte[]> readResponse = this.api.readObject( readRequest, byte[].class ); Assert.assertArrayEquals( "object content wrong", data, readResponse.getObject() ); Assert.assertEquals( "read checksums don't match", wsChecksum, readResponse.getWsChecksum() ); } /** * Tests getting the service information */ @Test public void testGetServiceInformation() throws Exception { ServiceInformation si = this.api.getServiceInformation(); Assert.assertNotNull( "Atmos version is null", si.getAtmosVersion() ); } /** * Test getting object info. Note to fully run this testcase, you should * create a policy named 'retaindelete' that keys off of the metadata * policy=retaindelete that includes a retention and deletion criteria. */ @Test public void testGetObjectInfo() throws Exception { CreateObjectRequest request = new CreateObjectRequest(); request.content( "hello".getBytes( "UTF-8" ) ).contentType( "text/plain" ); ObjectId id = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); api.setUserMetadata( id, new Metadata( "policy", "retain", false ) ); // Read back the content String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); // Check policyname Map<String,Metadata> sysmeta = this.api.getSystemMetadata(id, "policyname"); Assert.assertNotNull("Missing system metadata 'policyname'", sysmeta.get("policyname") ); // Get the object info ObjectInfo oi = this.api.getObjectInfo( id ); Assert.assertNotNull( "ObjectInfo null", oi ); Assert.assertNotNull( "ObjectInfo objectid null", oi.getObjectId() ); Assert.assertTrue( "ObjectInfo numReplicas is 0", oi.getNumReplicas() > 0 ); Assert.assertNotNull( "ObjectInfo replicas null", oi.getReplicas() ); Assert.assertNotNull( "ObjectInfo selection null", oi.getSelection() ); Assert.assertTrue( "ObjectInfo should have at least one replica", oi.getReplicas().size() > 0 ); // only run these tests if the policy configuration is valid Assume.assumeTrue("policyname != retaindelete", "retaindelete".equals(sysmeta.get("policyname").getValue())); Assert.assertNotNull( "ObjectInfo expiration null", oi.getExpiration().getEndAt() ); Assert.assertNotNull( "ObjectInfo retention null", oi.getRetention().getEndAt() ); api.setUserMetadata( id, new Metadata( "user.maui.retentionEnable", "false", false ) ); } @Test public void testHmac() throws Exception { // Compute the signature hash String input = "Hello World"; byte[] secret = Base64.decodeBase64( "D7qsp4j16PBHWSiUbc/bt3lbPBY=".getBytes( "UTF-8" ) ); Mac mac = Mac.getInstance( "HmacSHA1" ); SecretKeySpec key = new SecretKeySpec( secret, "HmacSHA1" ); mac.init( key ); l4j.debug( "Hashing: \n" + input ); byte[] hashData = mac.doFinal( input.getBytes( "ISO-8859-1" ) ); // Encode the hash in Base64. String hashOut = new String( Base64.encodeBase64( hashData ), "UTF-8" ); l4j.debug( "Hash: " + hashOut ); } @Test public void testDirectoryMetadata() throws Exception { ObjectPath dir = new ObjectPath( "/" + rand8char() + "/" ); Metadata listable = new Metadata( "listable", "foo", true ); Metadata unlistable = new Metadata( "unlistable", "bar", false ); Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true ); Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false ); Metadata listable3 = new Metadata( "listable3", null, true ); Metadata withCommas = new Metadata( "withcommas", "I, Robot", false ); Metadata withEquals = new Metadata( "withequals", "name=value", false ); ObjectId id = this.api.createDirectory( dir, null, listable, unlistable, listable2, unlistable2, listable3, withCommas, withEquals ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); // Read and validate the metadata Map<String, Metadata> metaMap = this.api.getObjectMetadata( dir ).getMetadata(); Assert.assertNotNull("Missing metadata 'listable'", metaMap.get( "listable" )); Assert.assertNotNull("Missing metadata 'listable2'", metaMap.get( "listable2" )); Assert.assertNotNull("Missing metadata 'unlistable'", metaMap.get( "unlistable" )); Assert.assertNotNull("Missing metadata 'unlistable2'", metaMap.get( "unlistable2" )); Assert.assertEquals( "value of 'listable' wrong", "foo", metaMap.get( "listable" ).getValue() ); Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", metaMap.get( "listable2" ).getValue() ); Assert.assertEquals( "value of 'unlistable' wrong", "bar", metaMap.get( "unlistable" ).getValue() ); Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", metaMap.get( "unlistable2" ).getValue() ); Assert.assertNotNull( "listable3 missing", metaMap.get( "listable3" ) ); Assert.assertTrue( "Value of listable3 should be empty", metaMap.get( "listable3" ).getValue() == null || metaMap.get( "listable3" ).getValue().length() == 0 ); Assert.assertEquals( "Value of withcommas wrong", "I, Robot", metaMap.get( "withcommas" ).getValue() ); Assert.assertEquals( "Value of withequals wrong", "name=value", metaMap.get( "withequals" ).getValue() ); // Check listable flags Assert.assertEquals( "'listable' is not listable", true, metaMap.get( "listable" ).isListable() ); Assert.assertEquals( "'listable2' is not listable", true, metaMap.get( "listable2" ).isListable() ); Assert.assertEquals( "'listable3' is not listable", true, metaMap.get( "listable3" ).isListable() ); Assert.assertEquals( "'unlistable' is listable", false, metaMap.get( "unlistable" ).isListable() ); Assert.assertEquals( "'unlistable2' is listable", false, metaMap.get( "unlistable2" ).isListable() ); } /** * Tests fetching data with multiple ranges. */ @Test public void testMultipleRanges() throws Exception { String input = "Four score and seven years ago"; ObjectId id = api.createObject( input.getBytes( "UTF-8" ), "text/plain" ); cleanup.add( id ); Assert.assertNotNull( "Object null", id ); Range[] ranges = new Range[5]; ranges[0] = new Range( 27, 28 ); //ag ranges[1] = new Range( 9, 9 ); // e ranges[2] = new Range( 5, 5 ); // s ranges[3] = new Range( 4, 4 ); // ' ' ranges[4] = new Range( 27, 29 ); // ago ReadObjectResponse<MultipartEntity> response = api.readObject( new ReadObjectRequest().identifier( id ) .ranges( ranges ), MultipartEntity.class ); String out = new String( response.getObject().aggregateBytes(), "UTF-8" ); Assert.assertEquals( "Content incorrect", "ages ago", out ); } //---------- Features supported by the Atmos 2.0 REST API. ----------\\ @Test public void testGetShareableUrlAndDisposition() throws Exception { Assume.assumeFalse(isVipr); // Create an object with content. String str = "Four score and twenty years ago"; ObjectId id = this.api.createObject( str.getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); cleanup.add( id ); String disposition = "attachment; filename=\"foo bar.txt\""; Calendar c = Calendar.getInstance(); c.add( Calendar.HOUR, 4 ); Date expiration = c.getTime(); URL u = this.api.getShareableUrl( id, expiration, disposition ); l4j.debug( "Sharable URL: " + u ); InputStream stream = (InputStream) u.getContent(); BufferedReader br = new BufferedReader( new InputStreamReader( stream ) ); String content = br.readLine(); l4j.debug( "Content: " + content ); Assert.assertEquals( "URL does not contain proper content", str, content ); } @Test public void testGetShareableUrlWithPathAndDisposition() throws Exception { Assume.assumeFalse(isVipr); // Create an object with content. String str = "Four score and twenty years ago"; ObjectPath op = new ObjectPath( "/" + rand8char() + ".txt" ); ObjectId id = this.api.createObject( op, str.getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); //cleanup.add( op ); String disposition = "attachment; filename=\"foo bar.txt\""; Calendar c = Calendar.getInstance(); c.add( Calendar.HOUR, 4 ); Date expiration = c.getTime(); URL u = this.api.getShareableUrl( op, expiration, disposition ); l4j.debug( "Sharable URL: " + u ); InputStream stream = (InputStream) u.getContent(); BufferedReader br = new BufferedReader( new InputStreamReader( stream ) ); String content = br.readLine(); l4j.debug( "Content: " + content ); Assert.assertEquals( "URL does not contain proper content", str, content ); } @Test public void testGetShareableUrlWithPathAndUTF8Disposition() throws Exception { // Create an object with content. Assume.assumeFalse(isVipr); String str = "Four score and twenty years ago"; ObjectPath op = new ObjectPath( "/" + rand8char() + ".txt" ); ObjectId id = this.api.createObject( op, str.getBytes( "UTF-8" ), "text/plain" ); Assert.assertNotNull( "null ID returned", id ); //cleanup.add( op ); // One cryllic, one accented, and one japanese character // RFC5987 String disposition = "attachment; filename=\"no UTF support.txt\"; filename*=UTF-8''" + URLEncoder.encode( "бöシ.txt", "UTF-8" ); Calendar c = Calendar.getInstance(); c.add( Calendar.HOUR, 4 ); Date expiration = c.getTime(); URL u = this.api.getShareableUrl( op, expiration, disposition ); l4j.debug( "Sharable URL: " + u ); InputStream stream = (InputStream) u.getContent(); BufferedReader br = new BufferedReader( new InputStreamReader( stream ) ); String content = br.readLine(); l4j.debug( "Content: " + content ); Assert.assertEquals( "URL does not contain proper content", str, content ); } @Test public void testGetServiceInformationFeatures() throws Exception { ServiceInformation info = this.api.getServiceInformation(); l4j.info( "Supported features: " + info.getFeatures() ); Assert.assertTrue( "Expected at least one feature", info.getFeatures().size() > 0 ); } @Test public void testBug23750() throws Exception { byte[] data = new byte[1000]; Arrays.fill( data, (byte) 0 ); Metadata meta = new Metadata( "test", null, true ); RunningChecksum sha1 = new RunningChecksum( ChecksumAlgorithm.SHA1 ); sha1.update( data, 0, data.length ); CreateObjectResponse response = this.api.createObject( new CreateObjectRequest().content( data ).wsChecksum( sha1 ).userMetadata( meta ) ); try { Range range = new Range( 1000, 1999 ); RunningChecksum sha0 = new RunningChecksum( ChecksumAlgorithm.SHA0 ); sha0.update( data, 0, 1000 ); this.api.updateObject( new UpdateObjectRequest().identifier( response.getObjectId() ).content( data ) .range( range ).wsChecksum( sha0 ).userMetadata( meta ) ); Assert.fail( "Should have triggered an exception" ); } catch ( AtmosException e ) { // expected } } @Test public void testCrudKeys() throws Exception { Assume.assumeFalse(isVipr); ObjectKey key = new ObjectKey( "Test_key-pool#@!$%^..", "KEY_TEST" ); String content = "Hello World!"; CreateObjectRequest request = new CreateObjectRequest().identifier( key ); request.content( content.getBytes( "UTF-8" ) ).contentType( "text/plain" ); ObjectId oid = this.api.createObject( request ).getObjectId(); Assert.assertNotNull( "Null object ID returned", oid ); cleanup.add( oid ); String readContent = new String( this.api.readObject( key, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "content mismatch", content, readContent ); content = "Hello Waldo!"; this.api.updateObject( new UpdateObjectRequest().identifier( key ).content( content.getBytes( "UTF-8" ) ) ); readContent = new String( this.api.readObject( key, null, byte[].class ), "UTF-8" ); Assert.assertEquals( "content mismatch", content, readContent ); this.api.delete( key ); try { this.api.readObject( key, null, byte[].class ); Assert.fail( "Object still exists" ); } catch ( AtmosException e ) { if ( e.getHttpCode() != 404 ) throw e; } } @Test public void testIssue9() throws Exception { int threadCount = 10; final int objectSize = 10 * 1000 * 1000; // not a power of 2 final AtmosApi atmosApi = api; final List<ObjectIdentifier> cleanupList = new ArrayList<ObjectIdentifier>(); ThreadPoolExecutor executor = new ThreadPoolExecutor( threadCount, threadCount, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() ); try { for ( int i = 0; i < threadCount; i++ ) { executor.execute( new Thread() { public void run() { CreateObjectRequest request = new CreateObjectRequest(); request.content( new RandomInputStream( objectSize ) ).contentLength( objectSize ) .userMetadata( new Metadata( "test-data", null, true ) ); ObjectId oid = atmosApi.createObject( request ).getObjectId(); cleanupList.add( oid ); } } ); } while ( true ) { Thread.sleep( 1000 ); if ( executor.getActiveCount() < 1 ) break; } } finally { executor.shutdown(); cleanup.addAll( cleanupList ); if ( cleanupList.size() < threadCount ) Assert.fail( "At least one thread failed" ); } } /** * Test handling signature failures. Should throw an exception with * error code 1032. */ @Test public void testSignatureFailure() throws Exception { byte[] goodSecret = config.getSecretKey(); try { // Fiddle with the secret key byte[] badSecret = Arrays.copyOf( goodSecret, goodSecret.length ); Arrays.fill( badSecret, 5, 10, (byte) 128 ); // indexes 5-9 will be 10000000 (binary) config.setSecretKey( badSecret ); testCreateEmptyObject(); Assert.fail( "Expected exception to be thrown" ); } catch ( AtmosException e ) { Assert.assertEquals( "Expected error code 1032 for signature failure", 1032, e.getErrorCode() ); } finally { config.setSecretKey( goodSecret ); } } /** * Test general HTTP errors by generating a 404. */ @Test public void testFourOhFour() throws Exception { try { // Fiddle with the context config.setContext( "/restttttttttt" ); testCreateEmptyObject(); Assert.fail( "Expected exception to be thrown" ); } catch ( AtmosException e ) { Assert.assertEquals( "Expected error code 404 for bad context root", 404, e.getHttpCode() ); } finally { config.setContext( AtmosConfig.DEFAULT_CONTEXT ); } } @Test public void testServerOffset() throws Exception { long offset = api.calculateServerClockSkew(); l4j.info( "Server offset: " + offset + " milliseconds" ); testCreateEmptyObject(); // make sure requests still work after setting clock skew } /** * NOTE: This method does not actually test that the custom headers are sent over the wire. Run tcpmon or wireshark * to verify */ @Test public void testCustomHeaders() throws Exception { final Map<String, String> customHeaders = new HashMap<String, String>(); customHeaders.put( "myCustomHeader", "Hello World!" ); ((AtmosApiClient) api).addClientFilter( new ClientFilter() { @Override public ClientResponse handle( ClientRequest clientRequest ) throws ClientHandlerException { for ( String name : customHeaders.keySet() ) clientRequest.getHeaders().add( name, customHeaders.get( name ) ); return getNext().handle( clientRequest ); } } ); api.getServiceInformation(); } @Test public void testServerGeneratedChecksum() throws Exception { Assume.assumeFalse(isVipr); byte[] data = "hello".getBytes( "UTF-8" ); // generate our own checksum RunningChecksum md5 = new RunningChecksum( ChecksumAlgorithm.MD5 ); md5.update( data, 0, data.length ); CreateObjectRequest request = new CreateObjectRequest().content( data ).contentType( "text/plain" ); request.setServerGeneratedChecksumAlgorithm( ChecksumAlgorithm.MD5 ); CreateObjectResponse response = this.api.createObject( request ); Assert.assertNotNull( "null ID returned", response.getObjectId() ); cleanup.add( response.getObjectId() ); // verify checksum Assert.assertEquals( md5.toString( false ), response.getServerGeneratedChecksum().toString( false ) ); // Read back the content ReadObjectRequest readRequest = new ReadObjectRequest().identifier( response.getObjectId() ); ReadObjectResponse<byte[]> readResponse = api.readObject( readRequest, byte[].class ); String content = new String( readResponse.getObject(), "UTF-8" ); Assert.assertEquals( "object content wrong", "hello", content ); // verify checksum Assert.assertEquals( md5.toString( false ), readResponse.getServerGeneratedChecksum().toString( false ) ); } @Ignore("Blocked by Bug 30073") @Test public void testReadAccessToken() throws Exception { Assume.assumeFalse(isVipr); ObjectPath parentDir = createTestDir("ReadAccessToken"); ObjectPath path = new ObjectPath( parentDir, "read_token \n,<x> test" ); ObjectId id = api.createObject( path, "hello", "text/plain" ); Calendar expiration = Calendar.getInstance(); expiration.add( Calendar.MINUTE, 5 ); // 5 minutes from now AccessTokenPolicy.Source source = new AccessTokenPolicy.Source(); source.setAllowList( Arrays.asList( "10.0.0.0/8" ) ); source.setDenyList( Arrays.asList( "1.1.1.1" ) ); AccessTokenPolicy.ContentLengthRange range = new AccessTokenPolicy.ContentLengthRange(); range.setFrom( 0 ); range.setTo( 1024 ); // 1KB AccessTokenPolicy policy = new AccessTokenPolicy(); policy.setExpiration( expiration.getTime() ); policy.setSource( source ); policy.setMaxDownloads( 2 ); policy.setMaxUploads( 0 ); policy.setContentLengthRange( range ); CreateAccessTokenRequest request = new CreateAccessTokenRequest().identifier( id ).policy( policy ); CreateAccessTokenResponse response = api.createAccessToken( request ); String content = StreamUtil.readAsString( response.getTokenUrl().openStream() ); Assert.assertEquals( "content from *id* access token doesn't match", content, "hello" ); api.deleteAccessToken( response.getTokenUrl() ); response = api.createAccessToken( new CreateAccessTokenRequest().identifier( path ).policy( policy ) ); content = StreamUtil.readAsString( response.getTokenUrl().openStream() ); Assert.assertEquals( "content from *path* access token doesn't match", content, "hello" ); GetAccessTokenResponse getResponse = api.getAccessToken( response.getTokenUrl() ); AccessToken token = getResponse.getToken(); api.deleteAccessToken( token.getId() ); Assert.assertEquals( "token ID doesn't match", RestUtil.lastPathElement( response.getTokenUrl().getPath() ), token.getId() ); policy.setMaxDownloads( policy.getMaxDownloads() - 1 ); // we already used one Assert.assertEquals( "policy differs", policy, token ); } @Ignore("Blocked by Bug 30073") @Test public void testWriteAccessToken() throws Exception { Assume.assumeFalse(isVipr); ObjectPath parentDir = createTestDir("WriteAccessToken"); ObjectPath path = new ObjectPath( parentDir, "write_token_test" ); Calendar expiration = Calendar.getInstance(); expiration.add( Calendar.MINUTE, 10 ); // 10 minutes from now AccessTokenPolicy.Source source = new AccessTokenPolicy.Source(); source.setAllowList( Arrays.asList( "10.0.0.0/8" ) ); source.setDenyList( Arrays.asList( "1.1.1.1" ) ); AccessTokenPolicy.ContentLengthRange range = new AccessTokenPolicy.ContentLengthRange(); range.setFrom( 0 ); range.setTo( 1024 ); // 1KB List<AccessTokenPolicy.FormField> formFields = new ArrayList<AccessTokenPolicy.FormField>(); AccessTokenPolicy.FormField formField = new AccessTokenPolicy.FormField(); formField.setName( "x-emc-meta" ); formField.setOptional( true ); formFields.add( formField ); formField = new AccessTokenPolicy.FormField(); formField.setName( "x-emc-listable-meta" ); formField.setOptional( true ); formFields.add( formField ); AccessTokenPolicy policy = new AccessTokenPolicy(); policy.setExpiration( expiration.getTime() ); policy.setSource( source ); policy.setMaxDownloads( 2 ); policy.setMaxUploads( 1 ); policy.setContentLengthRange( range ); policy.setFormFieldList( formFields ); CreateAccessTokenRequest request = new CreateAccessTokenRequest().identifier( path ).policy( policy ); URL tokenUrl = api.createAccessToken( request ).getTokenUrl(); Client client = Client.create(); // prepare upload form String content = "Anonymous Upload Test"; // note we have to specify content-disposition parameters in a specific order due to bug 27005 FormDataContentDisposition contentDisposition = new ReorderedFormDataContentDisposition( "form-data; name=\"data\"; filename=\"test.txt\"" ); BodyPart bodyPart = new BodyPart( content, MediaType.TEXT_PLAIN_TYPE ).contentDisposition( contentDisposition ); FormDataMultiPart form = new FormDataMultiPart(); form.field( "x-emc-meta", "color=gray,size=3,foo=bar" ) .field( "x-emc-listable-meta", "listable=" ) .bodyPart( bodyPart ); // upload ClientResponse clientResponse = client.resource( tokenUrl.toURI() ) .type( MediaType.MULTIPART_FORM_DATA_TYPE ) .post( ClientResponse.class, form ); Assert.assertEquals( "http status from upload is wrong", 201, clientResponse.getStatus() ); ObjectId oid = new ObjectId( RestUtil.lastPathElement( clientResponse.getLocation().getPath() ) ); cleanup.add( oid ); clientResponse = client.resource( tokenUrl.toURI() ).get( ClientResponse.class ); Assert.assertEquals( content, clientResponse.getEntity( String.class ) ); // verify upload/download counts changed AccessToken token = api.getAccessToken( tokenUrl ).getToken(); Assert.assertEquals( "upload count is wrong", 0, token.getMaxUploads() ); Assert.assertEquals( "download count is wrong", 1, token.getMaxDownloads() ); // read object via standard api (namespace) - just make sure it's there api.readObject( new ReadObjectRequest().identifier( path ), String.class ); // " " (objectspace) ReadObjectResponse<String> response = api.readObject( new ReadObjectRequest().identifier( oid ), String.class ); Assert.assertEquals( "content is wrong", content, response.getObject() ); Assert.assertNotNull( "metadata is null", response.getMetadata() ); Assert.assertEquals( "content-type is wrong", "text/plain", response.getMetadata().getContentType() ); Map<String, Metadata> meta = response.getMetadata().getMetadata(); Assert.assertTrue( "color missing from metadata", meta.containsKey( "color" ) ); Assert.assertTrue( "size missing from metadata", meta.containsKey( "size" ) ); Assert.assertTrue( "foo missing from metadata", meta.containsKey( "foo" ) ); api.deleteAccessToken( tokenUrl ); } @Ignore("Blocked by Bug 30073") @Test public void testListAccessTokens() throws Exception { Assume.assumeFalse(isVipr); ObjectPath parentDir = createTestDir("ListAccessTokens"); ObjectPath path = new ObjectPath( parentDir, "read_token_test" ); ObjectId id = api.createObject( path, "hello", "text/plain" ); Calendar expiration = Calendar.getInstance(); expiration.add( Calendar.MINUTE, 5 ); // 5 minutes from now AccessTokenPolicy.Source source = new AccessTokenPolicy.Source(); source.setAllowList( Arrays.asList( "10.0.0.0/8" ) ); source.setDenyList( Arrays.asList( "1.1.1.1" ) ); AccessTokenPolicy.ContentLengthRange range = new AccessTokenPolicy.ContentLengthRange(); range.setFrom( 0 ); range.setTo( 1024 ); // 1KB AccessTokenPolicy policy = new AccessTokenPolicy(); policy.setExpiration( expiration.getTime() ); policy.setSource( source ); policy.setMaxDownloads( 2 ); policy.setMaxUploads( 0 ); policy.setContentLengthRange( range ); CreateAccessTokenRequest request = new CreateAccessTokenRequest().identifier( id ).policy( policy ); URL tokenUrl1 = api.createAccessToken( request ).getTokenUrl(); request = new CreateAccessTokenRequest().identifier( path ).policy( policy ); URL tokenUrl2 = api.createAccessToken( request ).getTokenUrl(); ListAccessTokensResponse response = api.listAccessTokens( new ListAccessTokensRequest() ); Assert.assertNotNull( "access token list is null", response.getTokens() ); Assert.assertEquals( "access token count wrong", 2, response.getTokens().size() ); AccessToken token = response.getTokens().get( 0 ); Assert.assertEquals( "token ID doesn't match", RestUtil.lastPathElement( tokenUrl1.getPath() ), token.getId() ); Assert.assertEquals( "policy differs", policy, token ); token = response.getTokens().get( 1 ); Assert.assertEquals( "token ID doesn't match", RestUtil.lastPathElement( tokenUrl2.getPath() ), token.getId() ); Assert.assertEquals( "policy differs", policy, token ); } @Test public void testDisableSslValidation() throws Exception { Assume.assumeFalse(isVipr); config.setDisableSslValidation( true ); api = new AtmosApiClient( config ); List<URI> sslUris = new ArrayList<URI>(); for ( URI uri : config.getEndpoints() ) { sslUris.add( new URI( "https", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment() ) ); } config.setEndpoints( sslUris.toArray( new URI[sslUris.size()] ) ); cleanup.add( api.createObject( "Hello SSL!", null ) ); } @Test public void testRetryFilter() throws Exception { final int retries = 3, delay = 500; final String flagMessage = "XXXXX"; config.setEnableRetry( true ); config.setMaxRetries( retries ); config.setRetryDelayMillis( delay ); api = new AtmosApiClient( config ); CreateObjectRequest request = new CreateObjectRequest().contentLength( 1 ).contentType( "text/plain" ); try { api.createObject( request.content( new RetryInputStream( config, flagMessage ) ) ); Assert.fail( "Retried more than maxRetries times" ); } catch ( ClientHandlerException e ) { Assert.assertEquals( "Wrong exception thrown", flagMessage, e.getCause().getMessage() ); } config.setMaxRetries( retries + 1 ); ObjectId oid = api.createObject( request.content( new RetryInputStream( config, flagMessage ) ) ).getObjectId(); cleanup.add( oid ); byte[] content = api.readObject( oid, null, byte[].class ); Assert.assertEquals( "Content wrong size", 1, content.length ); Assert.assertEquals( "Wrong content", (byte) 65, content[0] ); try { api.createObject( request.content( new RetryInputStream( null, null ) { @Override public int read() throws IOException { switch ( callCount++ ) { case 0: throw new AtmosException( "should not retry", 400 ); case 1: return 65; } return -1; } } ) ); Assert.fail( "HTTP 400 was retried and should not be" ); } catch ( ClientHandlerException e ) { Assert.assertEquals( "Wrong http code", 400, ((AtmosException) e.getCause()).getHttpCode() ); } try { api.createObject( request.content( new RetryInputStream( null, null ) { @Override public int read() throws IOException { switch ( callCount++ ) { case 0: throw new RuntimeException( flagMessage ); case 1: return 65; } return -1; } } ) ); Assert.fail( "RuntimeException was retried and should not be" ); } catch ( ClientHandlerException e ) { Assert.assertEquals( "Wrong exception message", flagMessage, e.getCause().getMessage() ); } } @Test public void testExpect100Continue() throws Exception { config.setEnableExpect100Continue( true ); InputStream is = new RandomInputStream( 5 ); CreateObjectRequest request = new CreateObjectRequest().content( is ).contentLength( 5 ); // test success first since some load-balancers screw up the next request after an E: 100-C failure cleanup.add( api.createObject( request ).getObjectId() ); // now test failure String tokenId = config.getTokenId(); config.setTokenId( "bogustokenid" ); is = new RandomInputStream( 5 ); try { api.createObject( request ); } catch ( AtmosException e ) { Assert.assertEquals( "wrong error code", 1033, e.getErrorCode() ); Assert.assertEquals( "input stream was read", 5, is.available() ); } finally { config.setTokenId( tokenId ); is.close(); } } @Test public void testMultiThreadedBufferedWriter() throws Exception { int threadCount = 20; ThreadPoolExecutor executor = new ThreadPoolExecutor( threadCount, threadCount, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() ); // test with String List<Throwable> errorList = Collections.synchronizedList( new ArrayList<Throwable>() ); for ( int i = 0; i < threadCount; i++ ) { executor.execute( new ObjectTestThread<String>( "Test thread " + i, "text/plain", String.class, errorList ) ); } do { Thread.sleep( 500 ); } while ( executor.getActiveCount() > 0 ); if ( !errorList.isEmpty() ) { for ( Throwable t : errorList ) t.printStackTrace(); Assert.fail( "At least one thread failed" ); } // test with JAXB bean try { for ( int i = 0; i < threadCount; i++ ) { executor.execute( new ObjectTestThread<AccessTokenPolicy>( createTestTokenPolicy( "Test thread " + i, "x.x.x." + i ), "text/xml", AccessTokenPolicy.class, errorList ) ); } do { Thread.sleep( 500 ); } while ( executor.getActiveCount() > 0 ); } finally { executor.shutdown(); } if ( !errorList.isEmpty() ) { for ( Throwable t : errorList ) t.printStackTrace(); Assert.fail( "At least one thread failed" ); } } @Test public void testProxyConfiguration() { AtmosConfig config = AtmosClientFactory.getAtmosConfig(); URI proxyUri = config.getProxyUri(); // don't run this test without a proxy config Assume.assumeNotNull(proxyUri); // capture existing system props for safety String oldProxyHost = System.getProperty("http.proxyHost"); String oldProxyPort = System.getProperty("http.proxyPort"); try { // just create and delete an object in each scenario // 1) URLConnection - no proxy config.setProxyUri(null); AtmosApi atmos = new AtmosApiBasicClient(config); ObjectId oid = atmos.createObject("URLConnection with no proxy", "text/plain"); atmos.delete(oid); // 2) Apache - no proxy atmos = new AtmosApiClient(config); oid = atmos.createObject("Apache with no proxy", "text/plain"); atmos.delete(oid); // 3) URLConnection - with config proxy config.setProxyUri(proxyUri); atmos = new AtmosApiBasicClient(config); oid = atmos.createObject("URLConnection with config proxy", "text/plain"); atmos.delete(oid); // 4) Apache - with config proxy atmos = new AtmosApiClient(config); oid = atmos.createObject("Apache with config proxy", "text/plain"); atmos.delete(oid); // 5) URLConnection - with system props (old school) proxy config.setProxyUri(null); System.setProperty("http.proxyHost", proxyUri.getHost()); System.setProperty("http.proxyPort", ""+proxyUri.getPort()); atmos = new AtmosApiBasicClient(config); oid = atmos.createObject("URLConnection with sysprop proxy", "text/plain"); atmos.delete(oid); // 6) Apache - with system props (old school) proxy // can't specify proxy user/pass in system props atmos = new AtmosApiClient(config); oid = atmos.createObject("Apache with sysprop proxy", "text/plain"); atmos.delete(oid); } finally { // now play nice and reset old props if (oldProxyHost != null) System.setProperty("http.proxyHost", oldProxyHost); if (oldProxyPort != null) System.setProperty("http.proxyPort", oldProxyPort); } } @Test public void testRetention() throws Exception { Metadata retention = new Metadata("retentionperiod", "1year", false); CreateObjectRequest request = new CreateObjectRequest().content(null).userMetadata(retention); ObjectId oid = api.createObject(request.contentType("text/plain")).getObjectId(); cleanup.add(oid); Thread.sleep(2000); // make sure retention is enabled ObjectInfo info = api.getObjectInfo(oid); Assume.assumeTrue(info.getRetention().isEnabled()); Calendar newEnd = Calendar.getInstance(); newEnd.setTime(info.getRetainedUntil()); DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); String retentionEnd = "user.maui.retentionEnd"; newEnd.set(Calendar.HOUR, 0); newEnd.set(Calendar.MINUTE, 0); newEnd.set(Calendar.SECOND, 0); newEnd.set(Calendar.MILLISECOND, 0); newEnd.add(Calendar.DATE, -1); try { api.setUserMetadata(oid, new Metadata(retentionEnd, iso8601Format.format(newEnd.getTime()), false)); Assert.fail("should not be able to shorten retention period"); } catch (AtmosException e) { Assert.assertEquals("Wrong error code", 1002, e.getErrorCode()); } // disable retention so we can delete (won't work on compliant subtenants!) api.setUserMetadata(oid, new Metadata("user.maui.retentionEnable", "false", false)); } protected String rand8char() { Random r = new Random(); StringBuilder sb = new StringBuilder( 8 ); for ( int i = 0; i < 8; i++ ) { sb.append( (char) ('a' + r.nextInt( 26 )) ); } return sb.toString(); } private AccessTokenPolicy createTestTokenPolicy( String allow, String deny ) { AccessTokenPolicy.Source source = new AccessTokenPolicy.Source(); source.setAllowList( Arrays.asList( allow ) ); source.setDenyList( Arrays.asList( deny ) ); AccessTokenPolicy policy = new AccessTokenPolicy(); policy.setExpiration( new Date( 1355897000000L ) ); policy.setMaxDownloads( 5 ); policy.setMaxUploads( 10 ); policy.setSource( source ); return policy; } private class RetryInputStream extends InputStream { protected int callCount = 0; private long now; private long lastTime; private AtmosConfig config; private String flagMessage; public RetryInputStream( AtmosConfig config, String flagMessage ) { this.config = config; this.flagMessage = flagMessage; } @Override public int read() throws IOException { switch ( callCount++ ) { case 0: lastTime = System.currentTimeMillis(); throw new AtmosException( "foo", 500 ); case 1: now = System.currentTimeMillis(); Assert.assertTrue( "Retry delay for 500 error was not honored", now - lastTime >= config.getRetryDelayMillis() ); lastTime = now; throw new AtmosException( "bar", 500, 1040 ); case 2: now = System.currentTimeMillis(); Assert.assertTrue( "Retry delay for 1040 error was not honored", now - lastTime >= config.getRetryDelayMillis() + 300 ); lastTime = now; throw new IOException( "baz" ); case 3: now = System.currentTimeMillis(); Assert.assertTrue( "Retry delay for IOException was not honored", now - lastTime >= config.getRetryDelayMillis() ); lastTime = now; throw new AtmosException( flagMessage, 500 ); case 4: return 65; } return -1; } @Override public synchronized void reset() throws IOException { } @Override public boolean markSupported() { return true; } } private class ObjectTestThread<T> implements Runnable { private T content; private String contentType; private Class<T> objectType; private List<Throwable> errorList; public ObjectTestThread( T content, String contentType, Class<T> objectType, List<Throwable> errorList ) { this.content = content; this.contentType = contentType; this.objectType = objectType; this.errorList = errorList; } @Override public void run() { try { ObjectId oid = api.createObject( content, contentType ); cleanup.add( oid ); T readContent = api.readObject( oid, null, objectType ); Assert.assertEquals( "Content for object " + oid + " not equal", content, readContent ); } catch ( Throwable t ) { errorList.add( t ); } } } }