10 Storage

Oracle Mobile Cloud provides a Storage API for storing media in the cloud. As a mobile app developer, you can use this API in your mobile app to store and retrieve objects, such as files, text, images, and JSON objects.

What Can I Do with Storage?

The Storage API enables your mobile app to store, update, retrieve, and delete media, such as JSON objects, text files, and images, in collections in your Mobile Hub instance. The media are stored as opaque objects, which means that each object is stored and retrieved from the collection by a user- or system-generated GUID (globally unique ID). You use mobile user roles to control who can read and write the objects in the collection.

Note that this API isn’t intended to act as a database-as-a-service (DBaaS) solution by storing business data used by external systems, or to host HTML 5 applications like a content management system (CMS).

Android

Add an Object to a Collection

String text = "This is sample text file";
String name = "sampleText.txt";
StorageObject storageObject = new StorageObject(null, text.getBytes(), "text/plain");
storageObject.setDisplayName(name);

Fetch an Object

This fetches the storage object from a collection and reads its contents in a stream:

int i=0;
for (StorageObject storageObject: storageObjects) {
    i++;
    InputStream payload = storageObject.getPayloadStream();
    int n;
    char[] buffer = new char[1024 * 4];
    InputStreamReader reader = null;
    try {
        reader = new InputStreamReader(payload, "UTF8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    StringWriter writer = new StringWriter();
    assert reader != null;

    try {
        while (-1 != (n = reader.read(buffer))) {
            writer.write(buffer, 0, n);
        }
    }catch (IOException e){
        e.printStackTrace();
    }
    Logger.debug(TAG, "Storage Object "+i+"  "+writer.toString());
}

Get Multiple Objects from a Collection

List<storageObject> storageObjects = null;
try {
		Storage storage = mobileBackend.getServiceProxy(Storage.class);
    storageObjects = storageCollection.get(0,10,true);
} catch (ServiceProxyException e) {
    e.printStackTrace();
}

Get a Shared Collection

This gets a specific shared collection called sharedCollection:

StorageCollection storageCollection= null;
try {
Storage storage = mobileBackend.getServiceProxy(Storage.class);
    storageCollection = storage.getStorageCollection("sharedCollection");
} catch (ServiceProxyException e) {
    e.printStackTrace();
}

Retrieve an Object

private Storage mStorage;
private String collectionID = "YOUR_COLLECTION_ID";
private String objectID = "YOUR_OBJECT_ID";

...

try {
    //Initialize and obtain the storage client
    mStorage = MobileManager.getManager().getDefaultMobileBackend(this).getServiceProxy(Storage.class);
    //Fetch the collection
    StorageCollection collection = mStorage.getStorageCollection(collectionID);
    //Fetch the object
    StorageObject object = collection.get(objectID);
    //Get the payload
    InputStream payload = object.getPayloadStream();
    //Display the image
    ImageView imageView = (ImageView) findViewById(R.id.imageView);
    imageView.setImageBitmap(BitmapFactory.decodeStream(payload));

} catch (ServiceProxyException e) {
    e.printStackTrace();
}

Update an Object

StorageObject storageObject = null;
try {
Storage storage = mobileBackend.getServiceProxy(Storage.class);
storageObject = storageCollection.get("26651715-9259-4676-a035-df47ef3e7e79");
} catch (ServiceProxyException e) {
    e.printStackTrace();
}

String text = "This is modified text in a text file";

storageObject.setPayload(text.getBytes(), "text/plain");

try {
Storage storage = mobileBackend.getServiceProxy(Storage.class);
    storageCollection.put(storageObject);
} catch (ServiceProxyException e) {
    e.printStackTrace();
}

Upload a New Object to a Collection

try {
Storage storage = mobileBackend.getServiceProxy(Storage.class);
    storageCollection.post(storageObject);
} catch (ServiceProxyException e) {
    e.printStackTrace();
}

iOS

Add an Object to a Collection

- (void) uploadData{
 
    NSString* collection_Id = @"myCollection";
    NSString* payload = @"This is a simple text object";
    NSString* contentType = @"text/plain";

    if ( payload == nil || [payload isEqualToString:@""]) 
    {
        NSLog(@"There is nothing to upload");
    }
    else{
        
        // Get storage object.
        OMCStorage* storage = [mbe storage];
        
        // Get collection where you want to upload new data.
        OMCStorageCollection* aCollection = [storage getCollection:collection_Id];
        
        // Create new data from payload (in case your payload is not already in NSData format )
        NSData* payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
        OMCStorageObject* aObject = [[OMCStorageObject alloc] setPayloadFromData:payloadData
																									 withContentType:contentType];
        
        // Post data.
        [aCollection post:aObject];
        
        NSLog(@"Upload finished");
    }
}

Delete an Object

  NSString* collection_Id = @"";
  // Get your collection
  OMCStorageCollection* aCollection = [storage getCollection:collection_Id];

  // Create/Update an object with the same objectID.
  NSString* objectID = @"object2";
	BOOL isDeleteSuccessful = [aCollection deleteWithKey:objectID];

Download Data to a Collection

This downloads data from any storage collection where:

collectionID is the id for the target collection.

objectID is the id for the target object.

-(void) downloadData{

		NSString* collection_Id = @"";
 		NSString* object_Id = @"";
	
	//	Get storage object.
 		OMCStorage* storage = [mbe storage];

	//	Get your collection
 		OMCStorageCollection* aCollection = [storage getCollection:collection_Id];

	//	Get your object from your collection.
 		OMCStorageObject* anObject = [aCollection get:object_Id];

	//	Get the data from payload of your object.
 		NSData* data = [anObject getPayloadData];
		NSLog(@"Download finished");
}

Get a User Isolated Collection

NSString* collection_Id = @"";

 NSString* user_Id = @"";

 // Get user isolated collection.
 OMCStorageCollection* aCollection = [storage getCollection:collection_Id forUserId:user_Id];

Get Multiple Objects from a Collection

NSString* collection_Id = @"";

  // Get your collection.
  OMCStorageCollection* aCollection = [storage getCollection:collection_Id];

    NSUInteger offset = 0; NSUInteger limit = 10;
    NSArray<OMCStorageObject*>* objects = [collection get:offset withLimit:limit getAllObjects:NO];

Get Object Data as a Stream

 NSString* collection_Id = @"";

   OMCStorageCollection* aCollection = [storage getCollection:collection_Id];

    NSString* object_Id = @"";

   OMCStorageObject* anObject = [aCollection get:object_Id];

  NSInputStream* inStream = [anObject getPayloadStream];

Retrieve a Storage Object

- (void) downloadData{
    
    //Fill in IDs for collection and object.
    NSString* collection_Id = @"";
    NSString* object_Id = @"";

    // Get storage object.
 	   OMCStorage* storage = [mbe storage];

	   // Get your collection.
 	   OMCStorageCollection* aCollection = [storage getCollection:collection_Id];

	   // Get your object from your collection.
 	   OMCStorageObject* aObject = [aCollection get:object_Id];

	   // Get the data from  your object's payload.
 	   NSData* data = [aObject getPayloadData];
	   NSLog(@"Download finished");
}

Updating an Object

NSString* collection_Id = @"";

  // Get your collection.
  OMCStorageCollection* aCollection = [storage getCollection:collection_Id];

  // Create/Update object with the same objectID.
    NSString* objectID = @"";
    NSData* payload = [@"This is updated object" dataUsingEncoding: NSUTF8StringEncoding];
    OMCStorageObject* object = [[OMCStorageObject alloc] initPayload:objectID
                                                            withData:payload
                                                      andContentType:@"plain/text"];
    OMCStorageObject* returnedObject = [aCollection put:object];

Uploading Data to a Collection

-(void) uploadData{
		NSString* collection_Id = @"";
    NSString* payload = @"";
    NSString* contentType = @"";

    if ( payload == nil || [payload isEqualToString:@""]) 
    {
        NSLog(@"There is nothing to upload");
    }
    else{
        
        // Get the storage object from your MobileBackend object.
        OMCStorage* storage = [mbe storage];
        
        // Get the collection where you want to upload new data.
        OMCStorageCollection* aCollection = [storage getCollection:collection_Id];
        
        // Create new data from payload (in case your payload is not already in NSData format).
        NSData* payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
        OMCStorageObject* anObject = [[OMCStorageObject alloc] setPayloadFromData:payloadData
                                                                 withContentType:contentType];
        
        // Post data to the collection.
        [aCollection post:anObject];
        
        NSLog(@"Upload finished");
    }
}

Cordova, JavaScript, and TypeScript

Add an Object to a Collection

var obj = new mcs.StorageObject(collection);
obj.setDisplayName("XYZ.pdf");
obj.loadPayload("Hello World from Oracle Autonomous Mobile Cloud Enterprise Cordova SDK", "text/plain");

collection.postObject(obj).then(onSuccess, onFailure);
function onSuccess(collection) { 
	console.log(collection); 
	return collection; 
}

function onFailure(error) { 
	console.error(error); 
	return Promise.reject(error); 
} 

Delete an Object

collection.deleteObject(objectId)
.then(onDeleteObjectSuccess)
.catch(onDeleteObjectFailure);
function onDeleteObjectSuccess(response) { 
	console.log(response); 
return response; 
}

function onDeleteObjectFailure(error) { 
	console.error(error); 
	return Promise.reject(error); 
} 

Fetch an Object

This fetches the storage object from a collection and reads its contents in a stream:

collection.getObject(objectId, 'json')
.then(onGetObjectSuccess)
.catch(onGetObjectFailed);

function onGetObjectSuccess(object)
{ console.log(object); 
	return object; 
}

function onGetObjectFailed(error)
{ console.error(error); 
	return Promise.reject(error);
}

Get a Collection

var backend = mcs.mobileBackend;
backend.storage.getCollection(collectionName)
.then(onGetCollectionSuccess)
.catch(onGetCollectionFailed);

function onGetCollectionSuccess(collection){ 
	console.log(collection); 
	return collection; 
}

function onGetCollectionFailed(error) { 
	console.error(error); 
	return Promise.reject(error); 
}

Get an Object from a User Isolated Collection

This gets an object from a user isolated collection belonging to another user:

let backend = mcs.mobileBackend;
backend.storage.getCollection(collectionName, userId)
.then(onGetCollectionSuccess)
.catch(onGetCollectionFailed);

function onGetCollectionSuccess(collection) { 
	console.log(collection); 
	return collection; 
}

function onGetCollectionFailed(error){ 
	console.error(error); 
	return Promise.reject(error); 
}

Get Multiple Objects from a Collection

Gets a collection, then uses that collection to get multiple objects:

collection.getObjects(2, 3, false)
.then(onSuccess)
.catch(onFailure);

function onSuccess(collection) { 
	console.log(collection); 
	return collection; 
}

function onFailure(error) { 
	console.error(error); 
	return Promise.reject(error); 
} 

Update an Object

 collection.getObject(objectId)
.then(onGetObjectSuccess)
.then(onSaveObjectSuccess)
.catch(onGetObjectFail);

function onGetObjectSuccess(response){ 
	response.name = 'NewName'; 
	return collection.putObject(response); 
}

function onSaveObjectSuccess(response){ 
	console.log(response); 
	return response; 
}

function onGetObjectFail(error){ 
	console.error(error); 
	return Promise.reject(error); 
}

Custom Code

Retrieve and Store Collections and Objects

For information on how custom code can retrieve collection information and store and retrieve objects, see Accessing the Storage API from Custom Code.

REST API

Storage API Endpoints

The Storage API has endpoints for retrieving, paginating, and ordering collections and also for retrieving, updating, and removing objects.

Here, we give a brief overview of the Storage API endpoints.

Get a Single Collection

To get the metadata about a collection, such as ID, description, and whether it is user isolated, call the GET operation on the {collection} endpoint as follows:

GET {baseUri}/mobile/platform/storage/collections/{collection}
For example, for a collection named images:
GET {baseUri}/mobile/platform/storage/collections/images

Get All Collections Associated with a Mobile Backend

To get a list of the collections that are associated with a mobile backend, call the GET operation on the collections endpoint as follows:

GET {baseUri}/mobile/platform/storage/collections

Store an Object

The Storage API has two operations for creating objects. The operation that you use depends on if you want to specify the object’s ID or you want the ID to be generated automatically.

  • To specify the ID, use PUT, and put the ID in the URI as described in Specifying the Object Identifier. Note that you can use the If-None-Match header to ensure that you don’t overwrite an object that has the same ID, as described in Creating an Object (If One Doesn't Already Exist).

  • To generate an ID, use POST as described in Generating an Object Identifier .

When you create an object using your own ID, remember that, for shared collections, the ID must be unique to the collection. For user isolated collections, the ID must be unique to the user’s space.

Always include the Content-Type header to specify the media type of the object being stored. This property also specifies the media type to return when the object is requested. If you don’t include this header, then the content type defaults to application/octet-stream.

Note that Storage doesn’t transform or encode an object. Storage stores the exact bytes that you send in the request. For example, you can’t send a Base-64 encoded image and store it as a binary image by including a Content-Type header set to image/jpeg and a Content-Encoding header set to base64. You can use a custom API to perform the transformation for you, as shown in the code examples in storage.store(collectionId, object, options, httpOptions) .

Specify the Object Identifier

When performing a PUT operation, the identifier of the object corresponds to the last value specified in the URI. For example, to store an object with an ID called part1524:

PUT {baseUri}/mobile/platform/storage/collections/images/objects/part1524
Create an Object (If One Doesn't Already Exist)
Put the wildcard (*) character in the request's If-None-Match header to force the PUT operation to create the object with the specified object ID only if no other object exists with that ID. Specifying the wildcard causes the call to fail if another object already exists with the same ID. For example:
PUT {baseUri}/mobile/platform/storage/collections/images/objects/part1542

Headers:
  If-None-Match: *
Generate an Object Identifier
To generate the identifier for an object and then store the object, use the POST operation. Unlike the PUT operation, there’s no identifier specified at the end of the URI for a POST operation. For example:
POST {baseUri}/mobile/platform/storage/collections/images/objects

The URI that accesses the newly created object is returned through the Location header in the response, and the ID attribute is included in the response body.

What Happens When an Object is Created?

When an object is created:

  • The content is stored.

  • The value of the Content-Type field in the request is stored. (This becomes the Content-Type field definition returned when the object is requested using a GET operation.)

  • An entity tag (ETag) value is assigned.

  • The createdBy value is set to the user ID of the user who performed the create operation.

  • The createdOn value is set to the time the object was stored on the server.

Update an Object

Objects are updated using the PUT operation. For the PUT call, specify the same identifier that was specified or generated when the object was created. Because objects are opaque, updating an object completely replaces the previous contents.

What Happens When an Object Is Updated?

When a PUT is performed on an object, the following occurs:

  • The content is completely replaced.

  • The value of the ETag changes.

  • The modifiedBy value is set to the user ID for whom the mobile app performed the PUT operation.

  • The modifiedOn value is set to the time the object was stored on the server.

Optimistic Locking

Optimistic locking is a strategy to use when you want to update an object only if object was not updated by someone else after you originally retrieved it. To implement this strategy, do one of the following:

  • Put the timestamp of when you last retrieved the object in the If-Unmodified-Since header.

  • Put the object’s ETag in the If-None-Match header.

For example, if the ETag value from the previous call is 2, then the PUT operation in the following example is performed only when the If-None-Match value of "2" matches the ETag of the object (part1524). If the versions don’t match, then the call’s PUT operation isn’t performed and part1524 remains unchanged.
PUT{baseUri}/mobile/platform/storage/collections/images/objects/part1524

Headers:
  If-None-Match: \"2\"
You can get a similar result using If-Unmodified-Since:
PUT {baseUri}/mobile/platform/storage/collections/images/objects/part1524

Headers:
  If-Unmodified-Since: Mon,30 Jun 2014 19:43:31 GMT     

Retrieve a List of Objects

To get the metadata about a set of objects in a collection, use the GET operation on the /collections/{collection}/objects endpoint. This metadata includes the object’s ID, its name, and size. The metadata also includes the canonical link and self links. For a full list of properties, see Taking a Look at Object Metadata .

In this example, images is the name of a shared collection.
GET {baseURI}/mobile/platform/storage/collections/images/objects

If the collection is user isolated and you have READ_ALL or READ_WRITE_ALL access, then you must include the user query parameter and specify which user's objects you want listed, even if you want to see your own objects (use * to list all user’s objects). Note that you provide the user’s ID, not the user name. For example:

GET {baseURI}/mobile/platform/storage/collections/images/objects?user=0cea04ee-9e26-4de3-ad6b-00a66c8d3b96
Page Through a List of Objects

If you don’t want to see all the results, or if you want to get the results in small blocks, use the limit and offset query parameters to request a subset of items.

Use the limit parameter to restrict the number of items returned. The default is 100. Define offset as the zero-based starting point for the returned items. The returned JSON body contains links for retrieving both the next and previous sets of items.

The following example gets the metadata for 50 objects, starting with the 201st object.
Get {baseUri}/mobile/platform/storage/collections/images/objects?offset=200&limit=50
Order
Use the orderBy parameter to control the order of the returned items. You can specify which property to order on and specify whether to put the items in ascending (asc) or descending (desc) order:
Get {baseUri}/mobile/platform/storage/collections/images/objects?orderBy=contentLength:desc
You can sort by the name, modifiedBy, modifiedOn, createdBy, createdOn, or contentLength property.

Note that you can order by one property only (either asc or desc).

Query
Use the q query parameter to restrict the list of returned objects to the value specified for the id, name, createdBy, or modifiedBy attributes.
Get {baseUri}/mobile/platform/storage/collections/images/objects?q=part
The objects returned are based on a case-sensitive, partial match of the id, name, createdBy, and modifiedBy attributes. With this example, the results might include an item with an ID of part1524 and an item modified by bonapart.

Retrieve an Object

Use the GET operation to retrieve the entire object. When performing the GET operation, the identifier (such as part1524 in the following example) is specified at the end of the URI.

Storage always returns the exact bytes that were stored. If the Accepts header doesn’t match the Content-Type that the object was stored with, then it returns a 406 status code.

In this example, the object is returned only if the Etag does not match. You can use this strategy prevent re-fetching an object if it hasn’t changed.
Get {baseUri}/mobile/platform/storage/collections/images/objects/part1524

Headers:
  If-None-Match: \"2\"

Delete an Object

To remove an object from a collection, call the DELETE operation. Deleting an object is permanent. There’s no way to restore an object after you call this operation.
DELETE {baseUri}/mobile/platform/storage/collections/images/objects/part1524
To safely remove an object, use the If-None-Match header with the object’s ETag, or the If-Unmodified-Since header with the timestamp of when you last retrieved the object:
DELETE {baseUri}/mobile/platform/storage/collections/images/objects/part1524

Headers:
  If-None-Match: \"2\"

You can use these headers to prevent overriding a change that another user made after you originally retrieved the object.

Optimize Performance

You can use the Check If Exists, Get If Newer, and Read Part of an Object (Chunk Data) strategies discussed next to optimize performance when you retrieve an object:

Check If Exists

To check if an object exists, use the HEAD operation instead of a GET operation. The HEAD operation returns the same information except for the actual object value.

Put If Absent

You can use the If-None-Match header with a wildcard (*) value in a PUT operation to store an object only when (or if) it isn’t already included in the collection.

When you use this strategy, the call executes only when the ETag is absent, which is true only if the object does not exist.
PUT {baseUri}/mobile/platform/storage/collections/profiles/objects/uprofile

Headers:
  If-None-Match: *

In this example, if the uprofile object doesn’t have an ETag, then myProfile.txt is stored as the uprofile object.

Get If Newer

If you have already retrieved an object, and you want to re-fetch it only if it has changed, use the GET operation with the If-None-Match or If-Modified-Since header to retrieve the object only if there has been a change since the last time the object was fetched.

  • If-None-Match

    This example re-fetches the object only if the ETag is not 2.

    GET {baseUri}/mobile/platform/storage/collections/images/objects/part1542
    
    Headers:
      If-None-Match: \"2\"
  • If-Modified-Since

    This example re-fetches the object only if it was modified after the date and time specified. Otherwise, the response status is 304 not modified.

    GET {baseUri}/mobile/platform/storage/collections/images/objects/part1542
    
    Headers: 
      If-Modified-Since: Mon, 30 Jun 2014 19:43:31 GMT
Read Part of an Object (Chunk Data)

If the mobile app needs to get a large object like a video file, you can use the Range header to retrieve a subset of the object. This field lets the mobile app retrieve the data in chunks, rather than all at once, by requesting a subset of bytes. Using this strategy, you can start streaming a video, or start displaying the contents of a long list before you fetch the whole object.

Here are examples of byte-range specifier values:

  • First 100 bytes: bytes=0-99

  • Second 100 bytes: bytes=100-199

  • Last 100 bytes: bytes=-100

  • First 100 and last 100 bytes: bytes=0-99,-100

This example gets the first 100 and last 100 bytes of a profile to display a preview of the object’s contents:

GET {baseUri}mobile/platform/storage/collections/profiles/objects/uprofile

Headers:
  Range: bytes=0-99,-100 

Test Runtime Operations Using the Endpoints Page

You can test client REST calls for collections manually through a command line tool or utility, from a mobile app running on a device or simulator, or you can use the Endpoints page to test various operations.

Using the Endpoints page for the Storage API, you can try out basic collection calls, which would typically be exercised by a mobile app. These endpoints would be called directly by calling REST APIs, indirectly (by calling the client SDK), or through custom code. Instead of configuring a device or simulator, or entering the command manually, you can test the API by first entering mobile app user credentials and parameters appropriate to the call and then by clicking Test Endpoint. The page displays the payload and the status code.

You can access the Endpoints page by clicking Storage in Platform APIs section that is located at the bottom of the APIs page for a mobile backend. You can also open the page by clicking Storage in the Platform APIs section at the bottom of the APIs page. (You open this page by clicking This is an image of the hamburger icon that opens the sidebar menu. The menu items are Home, Applications, Analytics, and Administration. Under the Applications heading, Mobile Backends is selected. to open the side menu. You then click Development and then APIs).

Manage Collections

Mobile apps can only use collections that are associated with a backend. You can make this association by adding existing collections to the backend when you create it. You can also create new collections as part of this process.

You can also use the Storage configuration pages in the service UI to associate a collection with a backend, as well as create and configure a collection, and define whether the collection is shared or user isolated.

Shared and User Isolated Collections

A collection is either shared or user isolated.

When a collection is shared, no one owns the collection or an object, and the objects are kept in a shared space. Those with certain mobile user roles, permissions, and access to the backend, or anonymous access to the backend associated with the collection, can update an object. Note that in both shared and user isolated collections, each object has an ID that is unique to the collection.

When a collection is user isolated, users who have Read-Only (All Users) access can read objects in other users’ spaces. Users with Read-Write (All Users) access can both read and write objects in other users’ spaces. Anonymous access is not permitted in user isolated collections.

Let's look at some examples of this behavior using the following scenarios:

Shared Collection

An online magazine is leveraging the Storage API as a way for authors to submit, change, or read, articles. They’ve provisioned a shared collection called articles, as shown in the figure below.
  • Ben has contributed articles on bugs and bats, while Art has written about cows and dogs.

  • The dogs article is shared, allowing both Ben and Art to collaborate on it.

  • Art and Ben are able to modify any article regardless of who originally submitted it.

  • Dee can read all the articles, but she can't make changes.

However, if this shared collection is added to the Security_CollectionsAnonymousAccess environment policy, then Ben, Art, Dee or anyone who has access to the backend can submit, change, or read articles.

User Isolated Collection

An online magazine has provisioned a user isolated collection called Articles, as shown in the following figure.

  • Ben and Art can read and edit their articles, and upload new articles as well. They can’t read or write each other's files.

  • Dee can read only her article. Because her role is InactiveAuthor, which gives her Read-Only permission, she can't upload any new articles.

  • Eva, the editor, can make changes to any file and return it to the author's isolated space.

  • Raj, the publisher, can view all the articles, but he can't make changes.

  • Because users are isolated, the authors don't have to worry about naming conflicts with others. Objects in different isolation spaces can have the same name (as is the case for the “dogs” articles by Dee and Art).

  • Eva and Raj can access Ben, Art, and Dee’s objects only by specifying a user qualification parameter. When Eva wants to make changes to Art’s article, the call that enables her to write to Art’s user space must include Art’s ID.

Anonymous users don’t have access to user isolated collections. If a user isolated collection is added to the Security_CollectionsAnonymousAccess environment policy, it’s just ignored.

Permissions in Shared and User Isolated Collections

You can designate who can access and update objects in a collection by attaching access permissions to mobile user roles, or for anonymous access, by adding the shared collection name to the Security_CollectionsAnonymousAccess environment policy.

For example, to include the Articles collection use Security_CollectionsAnonymousAccess=Articles.

If the collection does not, or cannot permit anonymous access:

  • Art and Ben’s Author mobile user role is associated with the Read-Write permission.

    • In the shared collection, they can read and update any article within the shared collection.

    • In the user isolated collection, they can read and update their own articles.

  • In contrast, Dee has the InactiveAuthor mobile user role, which gives her Read-Only permission.

    • In the shared collection, Dee can read Art’s article about dogs, as well as various articles from either Art or Ben about bugs, cows, and bats. Unlike Ben or Art, she can’t delete articles or add new ones.

    • In the user isolated collection, she can read her own article about dogs, but she can’t read Art’s article about dogs.

  • For user isolated collections, mobile user roles that are associated with the Read-Only (All Users) permission can view any object. The Read-Write (All Users) permission allows users to view and update objects in other users’ spaces. Because her role as Editor has a Read-Write (All Users) permission, Eva can read and edit various authors’ files, such as those authored by Ben and Art.

Although different mobile user roles can grant access to the same objects in a collection, such as Eva (Editor), Ben (Author), and Art (also Author), in the user isolated collection, the objects remain in their respective isolated spaces.

Note that when anonymous access is allowed on a shared collection, access and the ability to update an object is granted to any authenticated user as well, regardless of role. This means adding a collection name to the Security_CollectionsAnonymousAccess environment policy overrides permissions given through roles. Take care when allowing anonymous access to a collection. Security is more limited than with role-based permissions.

Storage Configuration

The Storage configuration pages in the UI can help you with a variety of tasks, such as creating and editing collections, and associating backends with collections.

Storage Configuration for All Collections

There are two Storage configuration pages you can use: Manage all collections in your instance from the This is an image of the sidebar menu. > Mobile Apps >Development>Storage page. Manage collections for a specific backend from the Storage tab on the backend page.

To open the Storage page for all collections, click This is an image of the sidebar menu.> Development>Storage.

Using this page, you can create collections, edit existing ones, associate them with mobile backends, and publish them.

You can find out when the collections listed were created or updated and which backends are using them by first selecting a collection and then expanding Used By and History.

Storage Configuration for a Specific Backend

To manage collections for a specific backend, click This is an image of the sidebar menu. > Development>Backends > Storage. This page shows which collections are associated with the backend and allows you to create and update associated collections.

Define a Collection

The New Collection dialog lets you name a collection so that it can be identified in REST calls and designate it as shared or user isolated.

  1. Open the Storage page either from a mobile backend or by clicking Storage in the side menu, and click New Collection.

  2. Complete the New Collection dialog:

    1. Enter a name for your collection. This name is used to form the Universal Resource Identifier (URI) for the collection. Within the context of the API call, the collection name is referred to as the collection ID:
      {baseUri}/mobile/platform/storage/collections/{collection ID}
      For example, for a collection named FiF_UploadedImages (cloud storage of images uploaded from mobile apps), the URI call would look like this:
      {baseUri}/mobile/platform/storage/collections/FiF_UploadedImages

      For a closer look at Storage API syntax, see Storage API Endpoints.

    2. Choose the collection type: Shared or User Isolated. You can’t change the scope of the collection after you’ve set it. For details and examples, see Shared and User Isolated Collections.

    3. If needed, enter a short description for the purpose of the collection, to be displayed in the list of collections.

  3. Click Create.

When you initially create a collection, it’s in a draft state, in version 1.0.
  • You can modify the collection name, access permissions, and its contents. Remember, you can’t change the collection type after it’s created.

  • You can version a collection. You might want to increment a collection’s major and minor version numbers when you publish it or when you add new objects.

  • While in the draft state, a collection can be moved to the trash from the More menu.

Collection Metadata

In addition to the basic properties like size (in bytes), and description, the collection metadata includes the collection name that identifies it for REST calls.

When you create a collection, the Storage API defines it using the following metadata:
Property Value Type Description

description

string

The short description. This is an optional value.

id

string

The collection name, which is used in the uniform resource identifier (URI). For example:

{baseURI}/mobile/platform/storage/collections/{collection}

The collection name is case-sensitive, meaning that mycollection and Mycollection are two different collections.

Add Access Permissions to a Collection

Collection access is granted through anonymous user settings in the policy file, or managed by mobile user roles. Once a mobile user role is defined, you can also grant which roles can read and write objects in the collection. To see what mobile user roles are available, go to the My Profile UI and click Roles.

Anonymous Access to Collections

Anonymous access is often given to users who just want to check information on an app without having to log in or needing a defined role. Weather apps, where a user can check their local weather, are a good example of this.

Likewise, you can grant anonymous access to a shared collection. Once a shared collection is created, the administrator adds its name to the Security_CollectionsAnonymousAccess policy. You can then access the shared collection via the REST API or the client SDK for your mobile platform. Also, if you want to access this anonymous shared collection from the UI, a workaround is to grant Read-Write permission to any role on the properties page.

Keep in mind that when you add a shared collection to the policy, both anonymous and named users have access and read/write privileges to the collection.

Role-based Access to Collections

  1. In the Storage page, select a collection and then click Open.
  2. In the Properties page, specify one or more mobile user roles for each permission type.
    • Read-Only and Read-Write access apply to all collections (shared or user isolated).

    • You can specify Read-Only (All Users) and Read-Write (All Users) permissions only if the collection type is user-isolated.

    Permission Shared User Isolated

    Read-Only

    Read-only access to all of the objects in a collection. For example, both a field technician and a customer can read promotional material like coupons, but they can’t update them.

    Read-only access to a user isolated collection. When the Read-Only permission is applied to user isolated collections, for example, a customer can view images (like a coupon), but he can’t update them, or submit additional ones (only a user with Read-Write (All Users) privileges can add an object to the customer’s user space). Because this is a user isolated collection, the customer can view only his images (or other customer-specific objects that are intended only for him). The Read-Only permission also prevents him from adding additional work orders or deleting them.

    Read-Write

    A user can override any object in the collection.

    A user can override the objects in his isolated space. For example, a customer can update the images of broken appliances that he’s submitted. Because this is a user isolated collection, the images that he can add (and update) are intended only for him. Because these images exist in his isolated space, he can update these objects, but no one else’s. Likewise, he can add or delete images, but can’t do this in anyone else’s isolated space.

    Read-Only (All Users)

    NA

    A user can read objects in all spaces. For example, a field technician can see the images updated by any customer, but she can’t update them, delete them, or add new ones.

    Read-Write (All Users)

    NA

    A user can override objects in all spaces. If a field technician has Read-Write (All Users) permission, then she can update work orders submitted by any customer.

    By default, mobile users can’t access a collection until they’ve been assigned mobile user roles that are associated with the Read-Write, Read-Only, Read-Write (All Users) or Read-Only (All Users) permissions.

Add Objects to a Collection

You can populate a collection with objects.

These steps show how to add an object using the UI. When you add an object from the UI, the ID is generated automatically. If you want to assign a specific ID to an object, use the Storage API, the custom code SDK, or the client SDK for your mobile platform. For details, see Store an Object.
  1. On the Storage page, select a collection and click Open.
    • If this collection has no objects, click Upload Files and then browse to and retrieve the object. Click Open.
    • If this collection already has objects, click Upload in the Content page. Browse to and retrieve the object. Click Open.
  2. If the collection is shared, click Add. If you have the identity domain administrator role, you can also upload to user isolated collections. Add the user realm and user name to the User Name Required dialog, and click Ok. You can only select from users whose roles have been granted permission to the collection. (Assign these roles in the Properties page.)
  3. To view the object data, select it from the list.

To permanently remove an object from a collection, select it and click Delete.

Object Metadata

When you upload an object, the Content page displays basic metadata, such as size, content type, version information, and who uploaded it. Using this page, you can also delete unneeded objects, or filter them. Some functions in user isolated collections are only available if you have the identity domain administrator role.

Property Value Type Description/Usage

ID

string

The object name, which is used for operations on a single object. It is the last value specified in the URI.

Content Length

integer

The size, in bytes.

Content Type

media type

The media type for the data, such as image/jpeg for a JPEG image, or application/json for JSON.

ETag

string (an integer in quotes, for example, "17")

A value that represents the version of the object. It's used with the If-Match and If-None-Match HTTP request headers.

Created By

user name

The name of the user who uploaded the data.

Created On

time stamp (In ISO 8601)

The time that the object was most recently stored on the server. Time stamps are stored in UTC.

Modify By

user name

The name of the user who modified the object.

Modified On

time stamp (in ISO 8601)

The time when the server received a request for an object. Time stamps are stored in UTC.

User ID

string

For a user isolated collection, the ID of the user whose space the object is in.

Update the Collection

You can update the name, description and access to a collection. You can’t however, change the collection type.

  1. On the Storage page, select a collection and then click Open.
  2. Click Properties. (The Properties page opens by default when you first create a collection. On subsequent visits, the Content page opens by default.)
  3. Change the name, description or access as needed.
  4. Click Save.

Offline Data Storage

The client SDK’s Sync Client library, in conjunction with the Storage library, enables mobile apps to cache a collection’s objects for offline use and performance improvement. The apps can then use the cached objects instead of re-retrieving them from Storage, as described in How Synchronization Works with the Storage APIs. If a collection’s content changes infrequently, then consider enabling those mobile apps to cache the collection’s objects by selecting Enable the mobile client SDK to cache collection data locally for offline use.

When Enable the mobile client SDK to cache collection data locally for offline use is selected, the objects that a mobile app retrieves can remain in the cache for the period set in the Sync_CollectionTimeToLive policy. This value is conveyed to the app through the Oracle-Mobile-Sync-Expires response header. By default, the timeout period is set for 24 hours (86,400 seconds).

Don’t select this option for time-critical data, where a cached value might be misleading. For example, if the collection contains current stock prices, you shouldn’t select this option, because users expect the latest value (or no value at all).

If your mobile app isn’t using the client SDK’s Storage library, and your app is caching Storage objects, then you can take advantage of the following request and response headers:

Type Header Description
Request Oracle-Mobile-Sync-Agent When this header is set to true in the request, then the response includes either Oracle-Mobile-Sync-Expires or Oracle-Mobile-Sync-No-Store.
Response Oracle-Mobile-Sync-Expires Specifies when the returned resource must be marked as expired. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat. This value is determined by the Sync_CollectionTimeToLive policy.
Response Oracle-Mobile-Sync-No-Store When set to true, the client mustn’t cache the returned resource.

To learn more about data caching, see Data Offline and Sync .

Associate a Collection with a Backend

Associating a collection makes its contents available to a specific backend. The associated collection is a dependency.

  1. In the Storage page, select a collection.

  2. Click More and then select Associate Backends.

  3. In the Associate Backends dialog, select one or more backends from the list.

    The surrounding text describes this image.
  4. Click Add.

In the details pane, you can see any associated backends by expanding Used By.

You can also associate a collection with a backend this way:

  1. Open the backend.

  2. Click the Storage tab and then choose Select Collections.

  3. Choose one or more collections from the Select Collections dialog, and then click Select.

Remove a Collection from a Backend

You might want to disassociate a collection from a backend so that you can change the backend's state without affecting the collection. Or you might want to disassociate the collection and associate a different one.

  1. In the Storage page, select a collection.

  2. In the Details section on the right, view the Used By list.

  3. To delete the association, click the X that follows the backend version number.

  4. You’ll be prompted to remove the dependency. Click Remove.

To remove a collection from a backend:

  1. Open the backend.

  2. Open the Storage page.

  3. Click the X adjacent to the collection that you want to remove.

  4. In the Confirm Remove Dependency dialog, click Remove.