Skip to content

Commit

Permalink
[DMR]: Address apiview feedback. (#20205)
Browse files Browse the repository at this point in the history
  • Loading branch information
azabbasi authored Mar 29, 2021
1 parent 161a5c7 commit ff8a87e
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,6 @@ public static URI getModelUri(String dtmi, URI repositoryUri, boolean expanded)
}
}

/**
* Converts a string to {@link URI}
*
* @param uri String format of the path
* @return {@link URI} representation of the path/uri.
* @throws IllegalArgumentException If the {@code uri} is invalid.
*/
public static URI convertToUri(String uri) throws IllegalArgumentException {
try {
return new URI(uri);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid uri format", e);
}
}

static String dtmiToPath(String dtmi) {
if (!isValidDtmi(dtmi)) {
throw new IllegalArgumentException(String.format(StatusStrings.INVALID_DTMI_FORMAT_S, dtmi));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public final class ModelsRepositoryAsyncClient {
* Gets the repository uri that the client has been initialized with.
* @return The target repository uri.
*/
public URI getRepositoryUri() {
return this.repositoryUri;
public String getRepositoryUri() {
return this.repositoryUri.toString();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.util.Context;

import java.net.URI;
import java.util.Map;

/**
Expand All @@ -27,7 +26,7 @@ public final class ModelsRepositoryClient {
* Gets the repository uri that the client has been initialized with.
* @return The target repository uri.
*/
public URI getRepositoryUri() {
public String getRepositoryUri() {
return this.modelsRepositoryAsyncClient.getRepositoryUri();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,7 @@ public ModelsRepositoryClientBuilder modelDependencyResolution(ModelDependencyRe
* @return the updated ModelsRepositoryClientBuilder instance for fluent building.
*/
public ModelsRepositoryClientBuilder repositoryEndpoint(String repositoryEndpoint) {
DtmiConventions.convertToUri(repositoryEndpoint);
this.repositoryEndpoint = DtmiConventions.convertToUri(repositoryEndpoint);
this.repositoryEndpoint = convertToUri(repositoryEndpoint);
return this;
}

Expand Down Expand Up @@ -330,4 +329,19 @@ public ModelsRepositoryClientBuilder clientOptions(ClientOptions clientOptions)
this.clientOptions = clientOptions;
return this;
}

/**
* Converts a string to {@link URI}
*
* @param uri String format of the path
* @return {@link URI} representation of the path/uri.
* @throws IllegalArgumentException If the {@code uri} is invalid.
*/
static URI convertToUri(String uri) throws IllegalArgumentException {
try {
return new URI(uri);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid URI format", e);
}
}
}
116 changes: 63 additions & 53 deletions sdk/modelsrepository/azure-iot-modelsrepository/src/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,46 @@ The samples project demonstrates the following:
// When no URI is provided for instantiation, the Azure IoT Models Repository global endpoint
// https://devicemodels.azure.com/ is used and the model dependency resolution
// configuration is set to TryFromExpanded.
ModelsRepositoryClientBuilder clientBuilder = new ModelsRepositoryClientBuilder();
ModelsRepositoryAsyncClient asyncClient = clientBuilder.buildAsyncClient();
ModelsRepositoryClient syncClient = clientBuilder.buildClient();
ModelsRepositoryAsyncClient asyncClient = new ModelsRepositoryClientBuilder()
.buildAsyncClient();

System.out.println("Initialized the async client pointing to the global endpoint" + asyncClient.getRepositoryUri().toString());
System.out.println("Initialized the sync client pointing to the global endpoint" + syncClient.getRepositoryUri().toString());
ModelsRepositoryClient syncClient = new ModelsRepositoryClientBuilder()
.buildClient();

System.out.println("Initialized the async client pointing to the global endpoint" + asyncClient.getRepositoryUri());
System.out.println("Initialized the sync client pointing to the global endpoint" + syncClient.getRepositoryUri());
```

```java
// This form shows specifying a custom URI for the models repository with default client options.
// The default client options will enable model dependency resolution.
ModelsRepositoryAsyncClient asyncClient = new ModelsRepositoryClientBuilder()
.repositoryEndpoint("https://contoso.com/models")
.buildAsyncClient();

ModelsRepositoryClient syncClient = new ModelsRepositoryClientBuilder()
.repositoryEndpoint("https://contoso.com/models")
.buildClient();

System.out.println("Initialized the async client pointing to the custom endpoint" + asyncClient.getRepositoryUri());
System.out.println("Initialized the sync client pointing to the custom endpoint" + syncClient.getRepositoryUri());
```

```java
// The client will also work with a local file-system URI. This example shows initialization
// with a local URI and disabling model dependency resolution.
clientBuilder
.repositoryEndpoint(LOCAL_DIRECTORY_URI)
.modelDependencyResolution(ModelDependencyResolution.DISABLED);
asyncClient = clientBuilder.buildAsyncClient();
syncClient = clientBuilder.buildClient();

System.out.println("Initialized the async client pointing to the local file-system: " + asyncClient.getRepositoryUri().toString());
System.out.println("Initialized the sync client pointing to the local file-system: " + syncClient.getRepositoryUri().toString());
ModelsRepositoryAsyncClient asyncClient = new ModelsRepositoryClientBuilder()
.repositoryEndpoint(CLIENT_SAMPLES_DIRECTORY_PATH)
.modelDependencyResolution(ModelDependencyResolution.DISABLED)
.buildAsyncClient();

ModelsRepositoryClient syncClient = new ModelsRepositoryClientBuilder()
.repositoryEndpoint(CLIENT_SAMPLES_DIRECTORY_PATH)
.modelDependencyResolution(ModelDependencyResolution.DISABLED)
.buildClient();

System.out.println("Initialized the async client pointing to the local file-system: " + asyncClient.getRepositoryUri());
System.out.println("Initialized the sync client pointing to the local file-system: " + syncClient.getRepositoryUri());
```

## Sync vs Async clients
Expand Down Expand Up @@ -69,24 +90,20 @@ After publishing, your model(s) will be available for consumption from the globa

```java
// Global endpoint client
ModelsRepositoryClientBuilder clientBuilder = new ModelsRepositoryClientBuilder();
ModelsRepositoryAsyncClient asyncClient = clientBuilder.buildAsyncClient();
// Global endpoint client
ModelsRepositoryAsyncClient asyncClient = new ModelsRepositoryClientBuilder()
.buildAsyncClient();

// The output of getModels will include at least the definition for the target dtmi.
// If the model dependency resolution configuration is not disabled, then models in which the
// target dtmi depends on will also be included in the returned Map<String, String>.
String targetDtmi = "dtmi:com:example:TemperatureController;1";

CountDownLatch modelsCountdownLatch = new CountDownLatch(1);

// In this case the above dtmi has 2 model dependencies.
// dtmi:com:example:Thermostat;1 and dtmi:azure:DeviceManagement:DeviceInformation;1
asyncClient.getModels(targetDtmi)
.doOnSuccess(aVoid -> System.out.println("Fetched the model and dependencies for: " + targetDtmi))
.doOnTerminate(modelsCountdownLatch::countDown)
.subscribe(res -> System.out.println(String.format("%s resolved in %s interfaces.", targetDtmi, res.size())));

modelsCountdownLatch.await(MAX_WAIT_TIME_ASYNC_OPERATIONS_IN_SECONDS, TimeUnit.SECONDS);
```

GitHub pull-request workflows are a core aspect of the IoT Models Repository service. To submit models, the user is expected to fork and clone the global [models repository project][modelsrepository_github_repo] then iterate against the local copy. Changes would then be pushed to the fork (ideally in a new branch) and a PR created against the global repository.
Expand All @@ -95,60 +112,50 @@ To support testing local changes using this workflow and similar use cases, the

```java
// Local sample repository client
ModelsRepositoryClientBuilder clientBuilder = new ModelsRepositoryClientBuilder()
.repositoryEndpoint(LOCAL_DIRECTORY_URI);

ModelsRepositoryAsyncClient asyncClient = clientBuilder.buildAsyncClient();
ModelsRepositoryAsyncClient asyncClient = new ModelsRepositoryClientBuilder()
.repositoryEndpoint(CLIENT_SAMPLES_DIRECTORY_PATH)
.buildAsyncClient();

// The output of getModels will include at least the definition for the target dtmi.
// If the model dependency resolution configuration is not disabled, then models in which the
// target dtmi depends on will also be included in the returned Map<String, String>.
String targetDtmi = "dtmi:com:example:TemperatureController;1";

CountDownLatch modelsCountdownLatch = new CountDownLatch(1);

// In this case the above dtmi has 2 model dependencies.
// dtmi:com:example:Thermostat;1 and dtmi:azure:DeviceManagement:DeviceInformation;1
asyncClient.getModels(targetDtmi)
.doOnSuccess(aVoid -> System.out.println("Fetched the model and dependencies for: " + targetDtmi))
.doOnTerminate(modelsCountdownLatch::countDown)
.subscribe(res -> System.out.println(String.format("%s resolved in %s interfaces.", targetDtmi, res.size())));

modelsCountdownLatch.await(MAX_WAIT_TIME_ASYNC_OPERATIONS_IN_SECONDS, TimeUnit.SECONDS);
```

You are also able to get definitions for multiple root models at a time by leveraging
the `getModels` overload that supports an `Iterable`.

```java
// Global endpoint client
ModelsRepositoryClientBuilder clientBuilder = new ModelsRepositoryClientBuilder();
ModelsRepositoryAsyncClient asyncClient = clientBuilder.buildAsyncClient();
ModelsRepositoryAsyncClient asyncClient = new ModelsRepositoryClientBuilder()
.buildAsyncClient();

// When given an Iterable of dtmis, the output of getModels() will include at
// least the definitions of each dtmi enumerated in the Iterable.
// If the model dependency resolution configuration is not disabled, then models in which each
// enumerated dtmi depends on will also be included in the returned Map<String, String>.
Iterable<String> dtmis = Arrays.asList("dtmi:com:example:TemperatureController;1", "dtmi:com:example:azuresphere:sampledevice;1");

CountDownLatch modelsCountdownLatch = new CountDownLatch(1);

// In this case the dtmi "dtmi:com:example:TemperatureController;1" has 2 model dependencies
// and the dtmi "dtmi:com:example:azuresphere:sampledevice;1" has no additional dependencies.
// The returned Map<String, String> will include 4 models.
asyncClient.getModels(dtmis)
.doOnSuccess(aVoid -> System.out.println("Fetched the models and dependencies for: " + String.join(", ", dtmis)))
.doOnTerminate(modelsCountdownLatch::countDown)
.subscribe(res -> System.out.println(String.format("Dtmis %s resolved in %s interfaces.", String.join(", ", dtmis), res.size())));

modelsCountdownLatch.await(MAX_WAIT_TIME_ASYNC_OPERATIONS_IN_SECONDS, TimeUnit.SECONDS);
```

## DtmiConventions utility functions

The IoT Models Repository applies a set of conventions for organizing digital twin models. This package exposes a class
called `DtmiConventions` which exposes utility functions supporting these conventions. These same functions are used throughout the client.

`isValidDtmi` will ensure that the provided DigitalTwins Model Id (DTMI) has the correct format according to [DTDL specifications][dtdlv2_reference].
```java
// This snippet shows how to validate a given DTMI string is well-formed.

Expand All @@ -161,27 +168,30 @@ String invalidDtmi = "dtmi:com:example:Thermostat";
System.out.println(String.format("Dtmi %s is a valid dtmi: %s", invalidDtmi, DtmiConventions.isValidDtmi(invalidDtmi)));
```

`getModelUri` allows for constructing the path in which the provided DTMI is supposed to be located at based on a repository URI.
For instance: If the repository URI is `https://contoso.com/models/` and the DTMI (DigitalTwins Model Id) is `dtmi:com:example:Thermostat;1` then the path in which the file should exist will be `https://constoso.com/models/dtmi/com/example/thermostat-1.json`.

Same pattern is expected while working with the local file system.
For instance: If the repository URI is `file:///path/to/repository` and the DTMI (DigitalTwins Model Id) is `dtmi:com:example:Thermostat;1` then the path in which the file should exist will be `file:///path/to/repository/dtmi/com/example/thermostat-1.json`.

If the `boolean` parameter to the `getModelUri` method (`expanded`) is `true`, the result of the resolved path will have `.expanded.json` file extension instead of `.json`. This will allow for the `ModelsRepositoryClient` to only load the pre-computed dependency tree instead of the entire dependency tree. If the `.expanded.json` form of the model payload cannot be found, the client will load the full dependency tree as a fall back mechanism.

```java
// This snippet shows obtaining a fully qualified path to a model file.

// Local repository example:
try {
URI localRepositoryUri = new URI("file:///path/to/repository");
String fullyQualifiedModelUri = DtmiConventions.getModelUri("dtmi:com:example:Thermostat;1", localRepositoryUri, false).toString();

// Prints: file:///path/to/repository/dtmi/com/example/thermostat-1.json
System.out.println(fullyQualifiedModelUri);

// Remote repository example
URI remoteRepositoryUri = new URI("https://contoso.com/models/");
fullyQualifiedModelUri = DtmiConventions.getModelUri("dtmi:com:example:Thermostat;1", remoteRepositoryUri, false).toString();

// Prints: https://constoso.com/models/dtmi/com/example/thermostat-1.json
System.out.println(fullyQualifiedModelUri);
} catch (URISyntaxException ex) {
System.out.println("Invalid URI path has been used to instantiate the URI object. Exiting...");
return;
}
URI localRepositoryUri = new URI("file:///path/to/repository");
String fullyQualifiedModelUri = DtmiConventions.getModelUri("dtmi:com:example:Thermostat;1", localRepositoryUri, false).toString();

// Prints: file:///path/to/repository/dtmi/com/example/thermostat-1.json
System.out.println(fullyQualifiedModelUri);

// Remote repository example with expanded enabled
URI remoteRepositoryUri = new URI("https://contoso.com/models/");
fullyQualifiedModelUri = DtmiConventions.getModelUri("dtmi:com:example:Thermostat;1", remoteRepositoryUri, true).toString();

// Prints: https://constoso.com/models/dtmi/com/example/thermostat-1.expanded.json
System.out.println(fullyQualifiedModelUri);
```

<!-- LINKS -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ public static void getModelUri() {
// Prints: file:///path/to/repository/dtmi/com/example/thermostat-1.json
System.out.println(fullyQualifiedModelUri);

// Remote repository example
// Remote repository example with expanded enabled.
URI remoteRepositoryUri = new URI("https://contoso.com/models/");
fullyQualifiedModelUri = DtmiConventions.getModelUri("dtmi:com:example:Thermostat;1", remoteRepositoryUri, false).toString();
fullyQualifiedModelUri = DtmiConventions.getModelUri("dtmi:com:example:Thermostat;1", remoteRepositoryUri, true).toString();

// Prints: https://constoso.com/models/dtmi/com/example/thermostat-1.json
// Prints: https://constoso.com/models/dtmi/com/example/thermostat-1.expanded.json
System.out.println(fullyQualifiedModelUri);
} catch (URISyntaxException ex) {
System.out.println("Invalid URI path has been used to instantiate the URI object. Exiting...");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

package com.azure.iot.core;

import java.util.Scanner;

/**
* Entry point for running samples.
*/
public class Main {
static void main(String[] args) throws InterruptedException {
public static void main(String[] args) throws InterruptedException {

// DtmiConventions samples
DtmiConventionsSamples.isValidDtmi();
Expand All @@ -20,5 +22,10 @@ static void main(String[] args) throws InterruptedException {
ModelResolutionSamples.getModelsFromGlobalRepository();
ModelResolutionSamples.getModelsFromLocalRepository();
ModelResolutionSamples.getMultipleModelsFromGlobalRepository();

Scanner userInput = new Scanner(System.in);
System.out.println("Press any key to exit.");
userInput.nextLine();
System.exit(1);
}
}
Loading

0 comments on commit ff8a87e

Please sign in to comment.