Export (0) Print
Expand All

Controlling Access to Azure Tables with Java

Updated: October 1, 2014

This guide will show you how to perform common tasks using Azure tables. The samples are written in Java and use the Azure SDK for Java. The scenarios covered include adding, deleting, querying, and updating the information in tables. Access control is fundamental to using tables securely and productively in your Java applications and so samples are presented by the type of access control demonstrated: Shared Access Signatures and Stored Access policy. Anonymous users do not have access to Azure tables. The samples were all compiled and run as Java applications in Eclipse and using the Azure Toolkit for Eclipse with Java (by Microsoft Open Technologies).

After reviewing the samples in the section about shared access signatures, you should be able to accomplish the following tasks with Java.

  • Create a new table in your storage account.

  • Delete a table in your storage account.

  • Generate a shared access signature (basic SAS) giving irrevocable temporary access to a table.

  • Generate a SAS giving access to a limited range of partition keys and row keys in a table.

  • Add a new entity into a table.

  • Delete an entity from a table.

  • Update an existing entity in a table.

  • Query a specific entity in a table.

  • Query all entities in a given partition of a table.

  • Upload a batch of entities into a table.

  • Provide access to a table without revealing the Azure storage account key.

A shared access signature (SAS) contains all the information necessary to give any user controlled access to tables saved in an Azure storage account. There is no public access to tables for anonymous users that do not have a shared access signature or a stored access policy. A basic SAS is not associated with a shared access policy and will provide irrevocable access to any user in possession of the SAS for a limited time. A basic SAS has an expiration time which cannot be extended. Once a basic SAS has been distributed, it cannot be cancelled, access to the table can only be revoked prior to the expiration time by changing the Azure storage account key.

An application in possession of the Azure storage key can generate a basic SAS for controlling access to a table saved in the storage account by specifying one or more of the following constants in the SharedAccessTablePermissions enum of a SharedAccessTablePolicy object.

  • NONE – Grant no access permissions to this table.

  • ADD – Add entities to a table.

  • DELETE – Delete entities from a table.

  • QUERY – Query the entities in a table.

  • UPDATE – Modify the entities in a table.

For example, the following string represents a typical SAS containing all the information necessary to access the table named people.

tn=people&sp=raud&sv=2012-02-12&se=2013-05-15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z&sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P

The SAS can be appended to the public access url as query parameters to construct a SAS url. Because the SAS contains all the necessary information, any user in possession of this url has access. Note that to construct a working url, a query symbol (?) is required between the public access url and SAS.

http://contoso.table.core.windows.net/people?tn=people&sp=raud&sv=2012-02-12&se=2013-05-15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z&sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P

Once this url is distributed it cannot be cancelled or extended beyond the expiration time. It can give any application that obtains it add, delete, query, and update access to the table. It stops working after the expiration time and cannot be reactivated.

The following Java code creates a table named people if it does not already exist. It generates a basic SAS string with an applicable time span of 1 hour. An applicable time span greater than 1 hour may be specified. The period of applicability starts immediately and expires one hour later. During the applicable time span, any user in possession of the SAS has ADD, DELETE, QUERY, and UPDATE access to the table. Rerunning this code a second time before the first expiration time does not revoke the permissions conveyed by the first SAS and old urls will continue to work. After the expiration time, a url appended with the expired SAS will not work. If this code is rerun after the expiration time, it generates a new valid SAS which can give permissions for another hour, but the old SAS is not reactivated.

public class TableBasic
{
   public static void main(String[] args) throws InvalidKeyException, 
      URISyntaxException, StorageException 
   {
   Account creds = new Account();  //Account key required to create SAS            
   final String storageConnectionString = creds.getstorageconnectionstring();
   CloudStorageAccount storageAccount = 
      CloudStorageAccount.parse(storageConnectionString);
   CloudTableClient tabClient = storageAccount.createCloudTableClient();
   CloudTable table = tabClient.getTableReference("people");
   table.createIfNotExists();
   SharedAccessTablePolicy policy = new SharedAccessTablePolicy();
   GregorianCalendar calendar = 
      new GregorianCalendar(TimeZone.getTimeZone("UTC"));
   calendar.setTime(new Date());
   policy.setSharedAccessStartTime(calendar.getTime()); //Immediate applicability
   calendar.add(Calendar.HOUR, 1);  //Applicable time-span is 1 hour
   policy.setSharedAccessExpiryTime(calendar.getTime());
   //This SAS grants all the access permissions
   policy.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, 
      SharedAccessTablePermissions.DELETE, SharedAccessTablePermissions.QUERY, 
      SharedAccessTablePermissions.UPDATE));
   TablePermissions tabPermissions = new TablePermissions();
   table.uploadPermissions(tabPermissions);
   String sas = table.generateSharedAccessSignature(policy, null, null, null, 
      null, null);           
   System.out.println("The shared access signature for the " 
      + table.getName() + " table:");
   System.out.println(sas);
   }        
 }

The Account class provides the Azure storage account name and the account key. You may also add metadata when you create the table. See the section titled Controlling Access to Azure Blob Containers with Java for code examples of how to write an Account class and for adding and reading metadata.

During the one hour time span of SAS applicability, an application in possession of the SAS url for the table can write entries into the people table using Java code similar to the following. Each entry in the table is an instance of an entity class. For example, if the people table contains contact information for a group of people, the following code could be used to define an entity. Every entity is identified by its partitionKey and its rowKey.

public class Entity extends TableServiceEntity {
   public Entity(String lastName, String firstName) {
      this.partitionKey = lastName;
      this.rowKey = firstName;
    }
   public Entity() { }
   String email;
   String phoneNumber;

   public String getEmail() {
      return this.email;}

   public void setEmail(String email) {
      this.email = email;}

   public String getPhoneNumber() {
      return this.phoneNumber;}

   public void setPhoneNumber(String phoneNumber) {
      this.phoneNumber = phoneNumber;}
}  

You can grant different access permissions to different parts of the table by creating multiple shared access signatures. To limit the permissions granted by the shared access string to entities in a specified range of partition keys and row keys, modify the generateSharedAccessSignature method as follows in the previous code used to create the string.

String sas = table.generateSharedAccessSignature(policy, null, <startPartitionKey>, <startRowKey>, <enPartitionKey>, <endRowKey>);

This is possible to do with either shared access signatures or stored access policy. An example of granting different access permissions to different parts of a table is presented in the section that discusses stored access policy.

The following Java code enables a user in possession of the SAS to add a new entry to the table without knowledge of the Azure storage account key. Note that there would be a conflict error if you tried to add an entity that already exists. You should use a delete and replace operation to modify an existing entity.

public class AddEntity 
{
   public static void main(String[] args) throws URISyntaxException, 
      StorageException {
      String sas = "tn=people&sp=raud&sv=2012-02-12&se=2013-05-
         15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z
         &sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P";
      StorageCredentialsSharedAccessSignature credentials = 
         new StorageCredentialsSharedAccessSignature(sas);        
      URI baseuri = new URI("http://contoso.table.core.windows.net");
      CloudTableClient tableClient = new CloudTableClient(baseuri,credentials);
      CloudTable cloudTable = tableClient.getTableReference("people");
      Entity customer1 = new Entity("Zee", "Bill");
      customer1.setEmail("Billy@contoso.com");
      customer1.setPhoneNumber("425-555-0101");
      TableOperation insertCustomer1 = TableOperation.insert(customer1);
      cloudTable.execute(insertCustomer1);
      System.out.println("Add Entity has finished running.");
      }
}

The following Java code enables a user in possession of the SAS to retrieve the specific entry for “Bill Zee” from the people table without knowledge of the Azure storage account key.

public class GetEntity 
{
   public static void main(String[] args) throws URISyntaxException, 
      StorageException 
   {       
   String sas = "tn=people&sp=raud&sv=2012-02-12&se=2013-05-
      15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z
      &sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P";
   StorageCredentialsSharedAccessSignature credentials = 
      new StorageCredentialsSharedAccessSignature(sas);
   URI baseuri = new URI("http://contoso.table.core.windows.net");
   CloudTableClient tableClient = new CloudTableClient(baseuri,credentials);   
   CloudTable cloudTable = tableClient.getTableReference("people");
   TableOperation retrieveCustomer1 = TableOperation.retrieve("Zee","Bill",Entity.class);
   Entity specificEntity = cloudTable.execute(retrieveCustomer1).getResultAsType();
   System.out.println(specificEntity.getEmail());
   System.out.println(specificEntity.getPhoneNumber());
   System.out.println("Get Entity has finished running.");
   }
}

Your application can also use a TableBatchOperation to add a batch of new entries to the table as follows.

public class AddBatch 
{
   public static void main(String[] args) throws URISyntaxException, 
      StorageException 
   {
      String sas = "tn=people&sp=raud&sv=2012-02-12&se=2013-05-
         15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z
         &sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P";
       StorageCredentialsSharedAccessSignature credentials = 
         new StorageCredentialsSharedAccessSignature(sas);
      URI baseuri = new URI("http://contoso.table.core.windows.net");
      CloudTableClient tableClient = new CloudTableClient(baseuri,credentials);

      TableBatchOperation batchOperation1 = new TableBatchOperation();
      TableBatchOperation batchOperation2 = new TableBatchOperation();
   
      CloudTable cloudTable = tableClient.getTableReference("people");

      Entity customer1 = new Entity("Smith", "Jeff");
      customer1.setEmail("Jeff@contoso.com");
      customer1.setPhoneNumber("425-555-0104");
      batchOperation1.insert(customer1);

      Entity customer2 = new Entity("Smith", "Ben");
      customer2.setEmail("Ben@contoso.com");
      customer2.setPhoneNumber("425-555-0102");
      batchOperation1.insert(customer2);

      Entity customer3 = new Entity("Smith", "Denise");
      customer3.setEmail("Denise@contoso.com");
      customer3.setPhoneNumber("425-555-0103");
      batchOperation1.insert(customer3);

      Entity customer4 = new Entity("Smith", "Julie");
      customer4.setEmail("Julie@contoso.com");
      customer4.setPhoneNumber("425-555-0100");
      batchOperation1.insert(customer4);

      Entity customer5 = new Entity("Zee", "Laura");
      customer5.setEmail("Laura@contoso.com");
      customer5.setPhoneNumber("425-555-0101");
      batchOperation2.insert(customer5);

      Entity customer6 = new Entity("Zee", "Smith");
      customer6.setEmail("Smitty@contoso.com");
      customer6.setPhoneNumber("425-555-0101");
      batchOperation2.insert(customer6);

      // Insert people with "Smith" as a last name.
      cloudTable.execute(batchOperation1);          
      // Insert people with "Zee" as a last name.
      cloudTable.execute(batchOperation2);

      System.out.println("Add Batch has finished running.");
   }
}

Your application may retrieve only the entries for a particular partition by generating a filter condition based upon the value of the partition key. For example, the following Java code only returns the entries in the people table that are in the “Zee” partition.

public class GetPartition 
{
   public static void main(String[] args) throws URISyntaxException 
   {
      final String PARTITION_KEY = "PartitionKey";
      final String ROW_KEY = "RowKey";

      String sas = "tn=people&sp=raud&sv=2012-02-12&se=2013-05-
         15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z
         &sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P";
      StorageCredentialsSharedAccessSignature credentials = 
         new StorageCredentialsSharedAccessSignature(sas);
      URI baseuri = new URI("http://contoso.table.core.windows.net");

      CloudTableClient tableClient = new CloudTableClient(baseuri,credentials);
      CloudTable cloudTable = tableClient.getTableReference("people");

      String partitionFilter = TableQuery.generateFilterCondition(
            PARTITION_KEY,
            QueryComparisons.EQUAL,
            "Smith");
      TableQuery<Entity> partitionQuery =
            TableQuery.from(Entity.class).where(partitionFilter);
      for (Entity entity : cloudTable.execute(partitionQuery))
      {
         System.out.println(entity.getPartitionKey() + " " + entity.getRowKey()
               + "\t" + entity.getEmail() + "\t" + entity.getPhoneNumber());
      }
      System.out.println("Get Partition has finished running.");   
   }
}

To modify an existing entity in a table, your application retrieves it from the table, makes changes to the entity object, and saves the changes back to the table service with a replace operation. The following code changes an existing person's phone number. Note that there would be a conflict error if you tried to add an entity that already exists.

public class ModifyEntity
{
   public static void main(String[] args) throws URISyntaxException,
      StorageException 
   {       
      String sas = "tn=people&sp=raud&sv=2012-02-12&se=2013-05-
         15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z
         &sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P";
      StorageCredentialsSharedAccessSignature credentials = 
         new StorageCredentialsSharedAccessSignature(sas);
      URI baseuri = new URI("http://contoso.table.core.windows.net");
      CloudTableClient tableClient = new CloudTableClient(baseuri,credentials); 
      CloudTable cloudTable = tableClient.getTableReference("people");
      TableOperation retrieveCustomer1 = 
         TableOperation.retrieve("Zee","Smith",Entity.class);
      Entity specificEntity = 
            cloudTable.execute(retrieveCustomer1).getResultAsType();
      specificEntity.setPhoneNumber("555-555-5555");
      TableOperation replaceEntity = TableOperation.replace(specificEntity);
      cloudTable.execute(replaceEntity);
      System.out.println(specificEntity.getEmail());
      System.out.println(specificEntity.getPhoneNumber());
      System.out.println("Modify Entity has finished running.");   ning.");
   }
}

To delete an entity in a table, your application retrieves it from the table and then uses a table operation to delete it. The following code deletes the entity identified by the partitionKey and rowKey pair: “Zee” “Smith.”

public class DeleteEntity
{
   public static void main(String[] args) throws URISyntaxException, 
      StorageException 
   {       
      String sas = "tn=people&sp=raud&sv=2012-02-12&se=2013-05-
        15T17%3A20%3A36Z&st=2013-05-15T16%3A20%3A36Z
        &sig=nPPnPPnn5%5PPnPPnPPnPPPnnPPnPPnPnnnnPnn5nnPnP%5P";
      StorageCredentialsSharedAccessSignature credentials = 
          new StorageCredentialsSharedAccessSignature(sas);
      URI baseuri = new URI("http://contoso.table.core.windows.net");
      CloudTableClient tableClient = new CloudTableClient(baseuri,credentials); 
      CloudTable cloudTable = tableClient.getTableReference("people");
      TableOperation retrieveCustomer1 = 
         TableOperation.retrieve("Zee","Smith",Entity.class);
      Entity specificEntity = 
            cloudTable.execute(retrieveCustomer1).getResultAsType();
      TableOperation deleteEntity  = TableOperation.delete(specificEntity);
      cloudTable.execute(deleteEntity);
      System.out.println(specificEntity.getEmail());
      System.out.println(specificEntity.getPhoneNumber());
      System.out.println("Delete Entity has finished running.");
   }
}

The Azure storage account key is required to delete the entire table from the storage account. The following code deletes the table people from the storage account.

public class DeleteTable
{
   public static void main(String[] args) throws URISyntaxException, 
      StorageException, InvalidKeyException 
   {       
      // Account key required to delete a table
      Account creds = new Account();
      final String storageConnectionString = creds.getstorageconnectionstring();
      CloudStorageAccount storageAccount =  
         CloudStorageAccount.parse(storageConnectionString);
      CloudTableClient tableClient = storageAccount.createCloudTableClient();
      CloudTable table = tableClient.getTableReference("people");
      System.out.println(table.getName() +" exists? "+ table.exists());
      table.deleteIfExists();
      System.out.println(table.getName() +" exists?  "+ table.exists());
      System.out.println("Delete Table has finished running.");
   }  
}

After reviewing the samples in the stored access policy section, you should be able to accomplish the following tasks with Java.

  • Generate a policy SAS that gives access to a table that can be revoked, extended, or updated.

  • Cancel all table access urls after distribution without changing your Azure storage key.

  • Reactivate a table policy SAS after its expiration time.

  • Revoke or update table urls based on a policy SAS at any time.

  • Create multiple policies for a single table.

  • Use policy SAS and basic SAS together for maximum flexibility of table access control.

Your application can have greater control over the access to tables by using a policy SAS rather than a basic SAS. A basic SAS limits the time span of applicability and cannot be extended or cancelled once it has been distributed. A policy SAS can be cancelled or modified at any time. Because the policy is saved with the queue rather than the SAS url, the policy can be updated without having to change the SAS string (or the urls distributed to users.) When the policy for the container is updated, all old urls that have been constructed using the policy SAS are reactivated with the updated policy. You do not need to redistribute the policy SAS to users to update them.

The following Java code creates a table people1 with a policy named heath if people1 does not already exist. It generates a policy SAS string with an applicable time span of 3 hours. The period of applicability starts immediately and expires three hours later. During the applicable time span, any user in possession of this policy SAS has ADD, DELETE, QUERY, and UPDATE access to a specific range of entities in the table from partition keys “Smith” to “Smith” and from row keys “Denise” to “Julie .” After the expiration time, a url appended with the expired SAS will not work until it is reactivated by the storage account’s owner using the storage account key.

public class TablePolicy
{
   public static void main(String[] args) throws InvalidKeyException, 
      URISyntaxException, StorageException 
   {
      //Account key required to create SAS
      Account creds = new Account();
      final String storageConnectionString = creds.getstorageconnectionstring();
      CloudStorageAccount storageAccount =
            CloudStorageAccount.parse(storageConnectionString);
      CloudTableClient tabClient = storageAccount.createCloudTableClient();
      CloudTable table = tabClient.getTableReference("people1");
      table.createIfNotExists();
      SharedAccessTablePolicy policy = new SharedAccessTablePolicy();
      GregorianCalendar calendar =
            new GregorianCalendar(TimeZone.getTimeZone("UTC"));
      calendar.setTime(new Date());
      policy.setSharedAccessStartTime(calendar.getTime());
      calendar.add(Calendar.HOUR, 3);
      policy.setSharedAccessExpiryTime(calendar.getTime());
      policy.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD,
            SharedAccessTablePermissions.DELETE, SharedAccessTablePermissions.QUERY,
            SharedAccessTablePermissions.UPDATE));
      TablePermissions tabPermissions = new TablePermissions();
      tabPermissions.getSharedAccessPolicies().put("Heath", policy);
      table.uploadPermissions(tabPermissions);
      String sas = table.generateSharedAccessSignature(
            new SharedAccessTablePolicy(),"Heath", "Smith", "Denise",
            "Smith", "Julie"); //Valid for this partitionKey-rowKey range
      System.out.println("The stored access policy signature for the "
            + table.getName() + " table:");
      System.out.println(sas);
   } 
}

If people1 exists, this code adds the policy heath to the container. Because a basic SAS saves all its information in the SAS string and a policy SAS saves its information with the table, the access rights of unexpired basic SAS urls are unaffected by adding or modifying a policy SAS. You cannot revoke a basic SAS that has already been distributed by creating a policy SAS for the table.

If the previous Java code is rerun before or after the expiration time of the policy SAS, this will renew the heath policy SAS for an additional 3 hours and reactivate any revoked or expired heath urls that have been distributed. You can also update the permissions (i.e. add or remove access permissions) at any time by editing and rerunning this code.

To revoke and cancel all the urls that have been distributed with the heath policy SAS, rerun the code with an expiration time that is earlier than start time. For example, substitute the following to revoke the policy SAS and cancel all the permissions to urls that have been distributed to users.

policy.setSharedAccessStartTime(calendar.getTime());
calendar.add(Calendar.HOUR, -1); //Expiration time is earlier than start time
policy.setSharedAccessExpiryTime(calendar.getTime());  

You can also revoke a policy SAS after distribution by removing or replacing the policy in the container. If the code is rerun on people1, with the name of the policy changed from heath to baxter , the new policy baxter overwrites the old policy heath. This cancels all the urls already distributed with a heath policy SAS. However, the old urls associated with heath policy can be reactivated by rerunning the code a third time with the policy changed from baxter to heath (which cancels all the baxter urls.)

The policy SAS can be appended to the basic url for the table as query parameters to construct a decorated url with the controlled permissions in the same way as a basic SAS. Note that the SAS generated by the code above does not include the query symbol (?) required between the basic url and policy SAS. For example, the string for people1 would be similar to the following.

http://contoso.table.core.windows.net/people1?erk=Julie&tn=people3&srk=Denise&spk=Smith&epk=Smith&sv=2012-02-12&sig=PPPPPn5nnnn5nPnPPnnnnPnnP
PnnnPPnnn5PP55PPPP%5P&si=heath

Applications can use the policy SAS in exactly the same way as a basic SAS. Simply substitute the policy SAS string for the basic SAS string in the previous examples to control access with a shared access policy. Applications that have a policy SAS can perform add, delete, query, and update operations on entities in the table without knowledge of the Azure storage account key.

A useful pattern to implement flexible table access control is to specify up to four stored access policies for the table. This approach still leaves the ability to also distribute as necessary short-lived basic SAS urls for the table. The basic SAS urls can give access permissions independent of the policy. The following Java code demonstrates how two stored access policies can be saved for a single queue. The policy heath has a 5 hour life, but only grants access to query but not change the table.

public class TablePolicies
{
   public static void main(String[] args) throws InvalidKeyException, 
      URISyntaxException, StorageException 
   {
      // Account key required to create SAS
      Account creds = new Account();
      final String storageConnectionString = creds.getstorageconnectionstring();
      CloudStorageAccount storageAccount =
            CloudStorageAccount.parse(storageConnectionString);
      CloudTableClient tabClient = storageAccount.createCloudTableClient();
      CloudTable table = tabClient.getTableReference("people");
      table.createIfNotExists();
      SharedAccessTablePolicy policy = new SharedAccessTablePolicy();
      GregorianCalendar calendar =
            new GregorianCalendar(TimeZone.getTimeZone("UTC"));
      calendar.setTime(new Date());
      policy.setSharedAccessStartTime(calendar.getTime());
      // Applicable time-span is 1 hour
      calendar.add(Calendar.HOUR, 1);
      policy.setSharedAccessExpiryTime(calendar.getTime());
      // Baxter policy grants add, delete, query and update access
      policy.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD,
            SharedAccessTablePermissions.DELETE,
            SharedAccessTablePermissions.QUERY,
            SharedAccessTablePermissions.UPDATE));
      SharedAccessTablePolicy policy2 = new SharedAccessTablePolicy();
      GregorianCalendar calendar2 =
            new GregorianCalendar(TimeZone.getTimeZone("UTC"));
      calendar2.setTime(new Date());
      policy2.setSharedAccessStartTime(calendar2.getTime());
      // Applicable time-span is 5 hours
      calendar2.add(Calendar.HOUR, 5);
      policy2.setSharedAccessExpiryTime(calendar2.getTime());
      // Heath policy grants query access only
      policy2.setPermissions(EnumSet.of(SharedAccessTablePermissions.QUERY));
      TablePermissions tabPermissions = new TablePermissions();
      tabPermissions.getSharedAccessPolicies().put("Baxter", policy);
      tabPermissions.getSharedAccessPolicies().put("Heath", policy2);
      table.uploadPermissions(tabPermissions);
      String sas = table.generateSharedAccessSignature(
            new SharedAccessTablePolicy(),"Baxter", null, null, null, null);
      System.out.println("The stored access policy signature:");
      System.out.println(sas);
      String sas2 = table.generateSharedAccessSignature(new
            SharedAccessTablePolicy(),"Heath", null, null, null, null);
      System.out.println("The stored access policy2 signature:");
      System.out.println(sas2);
   } 
}

See Also

Show:
© 2014 Microsoft