Skip to content

Commit f447c7c

Browse files
authored
Enable sync stack for Form Recognizer (#32117)
1 parent 2c884a4 commit f447c7c

File tree

73 files changed

+1900
-426
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1900
-426
lines changed

sdk/formrecognizer/azure-ai-formrecognizer/TROUBLESHOOTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ try {
3333
With async clients, you can catch and handle exceptions in the error callbacks:
3434

3535
```java readme-sample-async-handlingException
36-
administrationAsyncClient.deleteModel("{modelId}")
36+
administrationAsyncClient.deleteDocumentModel("{modelId}")
3737
.doOnSuccess(
3838
ignored -> System.out.println("Success!"))
3939
.doOnError(

sdk/formrecognizer/azure-ai-formrecognizer/src/main/java/com/azure/ai/formrecognizer/documentanalysis/DocumentAnalysisAsyncClient.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Constants.DEFAULT_POLL_INTERVAL;
3535
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Utility.activationOperation;
36+
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Utility.getAnalyzeDocumentOptions;
3637
import static com.azure.core.util.FluxUtil.monoError;
3738

3839
/**
@@ -420,8 +421,4 @@ private Mono<PollResponse<OperationResult>> processAnalyzeModelResponse(
420421
}
421422
return Mono.just(new PollResponse<>(status, operationResultPollResponse.getValue()));
422423
}
423-
424-
private static AnalyzeDocumentOptions getAnalyzeDocumentOptions(AnalyzeDocumentOptions userProvidedOptions) {
425-
return userProvidedOptions == null ? new AnalyzeDocumentOptions() : userProvidedOptions;
426-
}
427424
}

sdk/formrecognizer/azure-ai-formrecognizer/src/main/java/com/azure/ai/formrecognizer/documentanalysis/DocumentAnalysisClient.java

+178-8
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,42 @@
44
package com.azure.ai.formrecognizer.documentanalysis;
55

66
import com.azure.ai.formrecognizer.documentanalysis.administration.models.OperationStatus;
7+
import com.azure.ai.formrecognizer.documentanalysis.implementation.FormRecognizerClientImpl;
8+
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.AnalyzeDocumentHeaders;
9+
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.AnalyzeDocumentRequest;
10+
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.AnalyzeResultOperation;
11+
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.ErrorResponseException;
12+
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.StringIndexType;
13+
import com.azure.ai.formrecognizer.documentanalysis.implementation.util.Transforms;
714
import com.azure.ai.formrecognizer.documentanalysis.models.AnalyzeDocumentOptions;
815
import com.azure.ai.formrecognizer.documentanalysis.models.AnalyzeResult;
916
import com.azure.ai.formrecognizer.documentanalysis.models.OperationResult;
1017
import com.azure.core.annotation.ReturnType;
1118
import com.azure.core.annotation.ServiceClient;
1219
import com.azure.core.annotation.ServiceMethod;
1320
import com.azure.core.exception.HttpResponseException;
21+
import com.azure.core.http.rest.Response;
22+
import com.azure.core.http.rest.ResponseBase;
1423
import com.azure.core.util.BinaryData;
1524
import com.azure.core.util.Context;
25+
import com.azure.core.util.CoreUtils;
26+
import com.azure.core.util.logging.ClientLogger;
27+
import com.azure.core.util.polling.LongRunningOperationStatus;
28+
import com.azure.core.util.polling.PollResponse;
29+
import com.azure.core.util.polling.PollingContext;
1630
import com.azure.core.util.polling.SyncPoller;
1731

32+
import java.util.List;
33+
import java.util.Objects;
34+
import java.util.function.BiFunction;
35+
import java.util.function.Function;
36+
37+
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Constants.DEFAULT_POLL_INTERVAL;
38+
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Transforms.getHttpResponseException;
39+
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Utility.enableSyncRestProxy;
40+
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Utility.getAnalyzeDocumentOptions;
41+
import static com.azure.ai.formrecognizer.documentanalysis.implementation.util.Utility.getTracingContext;
42+
1843
/**
1944
* This class provides a synchronous client that contains the operations that apply to Azure Form Recognizer.
2045
* Operations allowed by the client are analyzing information from documents and images using custom-built document
@@ -35,18 +60,18 @@
3560
*/
3661
@ServiceClient(builder = DocumentAnalysisClientBuilder.class)
3762
public final class DocumentAnalysisClient {
38-
private final DocumentAnalysisAsyncClient client;
63+
private static final ClientLogger LOGGER = new ClientLogger(DocumentAnalysisClient.class);
64+
private final FormRecognizerClientImpl service;
3965

4066
/**
4167
* Create a {@link DocumentAnalysisClient client} that sends requests to the Document Analysis service's endpoint.
4268
* Each service call goes through the {@link DocumentAnalysisClientBuilder#pipeline http pipeline}.
4369
*
44-
* @param client The {@link DocumentAnalysisClient} that the client routes its request through.
70+
* @param service The proxy service used to perform REST calls.
4571
*/
46-
DocumentAnalysisClient(DocumentAnalysisAsyncClient client) {
47-
this.client = client;
72+
DocumentAnalysisClient(FormRecognizerClientImpl service) {
73+
this.service = service;
4874
}
49-
5075
/**
5176
* Analyzes data from documents with optical character recognition (OCR) and semantic values from a given document
5277
* using any of the prebuilt models or a custom-built analysis model.
@@ -128,8 +153,34 @@ public final class DocumentAnalysisClient {
128153
public SyncPoller<OperationResult, AnalyzeResult>
129154
beginAnalyzeDocumentFromUrl(String modelId, String documentUrl,
130155
AnalyzeDocumentOptions analyzeDocumentOptions, Context context) {
131-
return client.beginAnalyzeDocumentFromUrl(documentUrl, modelId,
132-
analyzeDocumentOptions, context).getSyncPoller();
156+
return beginAnalyzeDocumentFromUrlSync(documentUrl, modelId,
157+
analyzeDocumentOptions, context);
158+
}
159+
160+
SyncPoller<OperationResult, AnalyzeResult> beginAnalyzeDocumentFromUrlSync(String documentUrl, String modelId,
161+
AnalyzeDocumentOptions analyzeDocumentOptions, Context context) {
162+
if (CoreUtils.isNullOrEmpty(documentUrl)) {
163+
throw LOGGER.logExceptionAsError(new IllegalArgumentException("'documentUrl' is required and cannot"
164+
+ " be null or empty"));
165+
}
166+
if (CoreUtils.isNullOrEmpty(modelId)) {
167+
throw LOGGER.logExceptionAsError(new IllegalArgumentException("'modelId' is required and cannot"
168+
+ " be null or empty"));
169+
}
170+
final AnalyzeDocumentOptions finalAnalyzeDocumentOptions = getAnalyzeDocumentOptions(analyzeDocumentOptions);
171+
context = enableSyncRestProxy(getTracingContext(context));
172+
Context finalContext = context;
173+
return SyncPoller.createPoller(
174+
DEFAULT_POLL_INTERVAL,
175+
cxt -> new PollResponse<>(LongRunningOperationStatus.NOT_STARTED, analyzeActivationOperation(modelId,
176+
finalAnalyzeDocumentOptions.getPages(),
177+
finalAnalyzeDocumentOptions.getLocale(),
178+
null,
179+
documentUrl,
180+
finalContext).apply(cxt)),
181+
pollingOperation(modelId, finalContext),
182+
getCancellationIsNotSupported(),
183+
fetchingOperation(modelId, finalContext));
133184
}
134185

135186
/**
@@ -214,6 +265,125 @@ public final class DocumentAnalysisClient {
214265
public SyncPoller<OperationResult, AnalyzeResult>
215266
beginAnalyzeDocument(String modelId, BinaryData document,
216267
AnalyzeDocumentOptions analyzeDocumentOptions, Context context) {
217-
return client.beginAnalyzeDocument(modelId, document, analyzeDocumentOptions, context).getSyncPoller();
268+
Objects.requireNonNull(document, "'document' is required and cannot be null.");
269+
if (CoreUtils.isNullOrEmpty(modelId)) {
270+
throw LOGGER.logExceptionAsError(new IllegalArgumentException("'modelId' is required and cannot"
271+
+ " be null or empty"));
272+
}
273+
274+
final AnalyzeDocumentOptions finalAnalyzeDocumentOptions = getAnalyzeDocumentOptions(analyzeDocumentOptions);
275+
context = enableSyncRestProxy(getTracingContext(context));
276+
Context finalContext = context;
277+
return SyncPoller.createPoller(
278+
DEFAULT_POLL_INTERVAL,
279+
cxt -> new PollResponse<>(LongRunningOperationStatus.NOT_STARTED, analyzeActivationOperation(modelId,
280+
finalAnalyzeDocumentOptions.getPages(),
281+
finalAnalyzeDocumentOptions.getLocale(),
282+
document,
283+
null,
284+
finalContext).apply(cxt)),
285+
pollingOperation(modelId, finalContext),
286+
getCancellationIsNotSupported(),
287+
fetchingOperation(modelId, finalContext));
288+
}
289+
290+
private Function<PollingContext<OperationResult>, OperationResult> analyzeActivationOperation(
291+
String modelId, List<String> pages, String locale, BinaryData document, String documentUrl, Context context) {
292+
return (pollingContext) ->
293+
Transforms.toDocumentOperationResult(analyzeDocument(modelId,
294+
CoreUtils.isNullOrEmpty(pages) ? null : String.join(",", pages),
295+
locale,
296+
document,
297+
documentUrl,
298+
context)
299+
.getDeserializedHeaders().getOperationLocation());
300+
}
301+
302+
private ResponseBase<AnalyzeDocumentHeaders, Void> analyzeDocument(String modelId, String pages, String locale,
303+
BinaryData document, String documentUrl, Context context) {
304+
try {
305+
if (documentUrl == null) {
306+
return service.analyzeDocumentWithResponse(modelId,
307+
null,
308+
pages,
309+
locale,
310+
StringIndexType.UTF16CODE_UNIT,
311+
document,
312+
document.getLength(),
313+
context);
314+
} else {
315+
return service.analyzeDocumentWithResponse(modelId,
316+
pages,
317+
locale,
318+
StringIndexType.UTF16CODE_UNIT,
319+
new AnalyzeDocumentRequest().setUrlSource(documentUrl),
320+
context);
321+
}
322+
} catch (ErrorResponseException ex) {
323+
throw LOGGER.logExceptionAsError(getHttpResponseException(ex));
324+
325+
}
326+
}
327+
private BiFunction<PollingContext<OperationResult>, PollResponse<OperationResult>, OperationResult>
328+
getCancellationIsNotSupported() {
329+
return (pollingContext, activationResponse) -> {
330+
throw LOGGER.logExceptionAsError(new RuntimeException("Cancellation is not supported"));
331+
};
332+
}
333+
334+
private Function<PollingContext<OperationResult>, PollResponse<OperationResult>>
335+
pollingOperation(String modelId, Context finalContext) {
336+
return pollingContext -> {
337+
final PollResponse<OperationResult> operationResultPollResponse
338+
= pollingContext.getLatestResponse();
339+
final String resultId = operationResultPollResponse.getValue().getOperationId();
340+
Response<AnalyzeResultOperation> modelResponse;
341+
try {
342+
modelResponse = service.getAnalyzeDocumentResultWithResponse(modelId, resultId, finalContext);
343+
} catch (ErrorResponseException ex) {
344+
throw LOGGER.logExceptionAsError(Transforms.getHttpResponseException(ex));
345+
}
346+
return processAnalyzeModelResponse(modelResponse, operationResultPollResponse);
347+
};
348+
}
349+
350+
private PollResponse<OperationResult> processAnalyzeModelResponse(
351+
Response<AnalyzeResultOperation> analyzeResultOperationResponse,
352+
PollResponse<OperationResult> operationResultPollResponse) {
353+
LongRunningOperationStatus status;
354+
switch (analyzeResultOperationResponse.getValue().getStatus()) {
355+
case NOT_STARTED:
356+
case RUNNING:
357+
status = LongRunningOperationStatus.IN_PROGRESS;
358+
break;
359+
case SUCCEEDED:
360+
status = LongRunningOperationStatus.SUCCESSFULLY_COMPLETED;
361+
break;
362+
case FAILED:
363+
throw LOGGER.logExceptionAsError(Transforms
364+
.mapResponseErrorToHttpResponseException(analyzeResultOperationResponse.getValue().getError()));
365+
default:
366+
status = LongRunningOperationStatus.fromString(
367+
analyzeResultOperationResponse.getValue().getStatus().toString(), true);
368+
break;
369+
}
370+
return new PollResponse<>(status, operationResultPollResponse.getValue());
371+
}
372+
373+
private Function<PollingContext<OperationResult>, AnalyzeResult>
374+
fetchingOperation(
375+
String modelId, Context finalContext) {
376+
return pollingContext -> {
377+
final String resultId = pollingContext.getLatestResponse().getValue().getOperationId();
378+
try {
379+
return Transforms.toAnalyzeResultOperation(service.getAnalyzeDocumentResultWithResponse(
380+
modelId,
381+
resultId,
382+
finalContext)
383+
.getValue().getAnalyzeResult());
384+
} catch (ErrorResponseException ex) {
385+
throw LOGGER.logExceptionAsError(Transforms.getHttpResponseException(ex));
386+
}
387+
};
218388
}
219389
}

sdk/formrecognizer/azure-ai-formrecognizer/src/main/java/com/azure/ai/formrecognizer/documentanalysis/DocumentAnalysisClientBuilder.java

+35-9
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,28 @@ public final class DocumentAnalysisClientBuilder implements
138138
* and {@link #retryPolicy(RetryPolicy)} have been set.
139139
*/
140140
public DocumentAnalysisClient buildClient() {
141-
return new DocumentAnalysisClient(buildAsyncClient());
141+
// Endpoint cannot be null, which is required in request authentication
142+
Objects.requireNonNull(endpoint, "'Endpoint' is required and can not be null.");
143+
if (audience == null) {
144+
audience = DocumentAnalysisAudience.AZURE_PUBLIC_CLOUD;
145+
}
146+
// Global Env configuration store
147+
final Configuration buildConfiguration = (configuration == null)
148+
? Configuration.getGlobalConfiguration().clone() : configuration;
149+
150+
// Service Version
151+
final DocumentAnalysisServiceVersion serviceVersion =
152+
version != null ? version : DocumentAnalysisServiceVersion.getLatest();
153+
154+
HttpPipeline pipeline = getHttpPipeline(buildConfiguration);
155+
156+
final FormRecognizerClientImpl formRecognizerAPI = new FormRecognizerClientImplBuilder()
157+
.endpoint(endpoint)
158+
.apiVersion(serviceVersion.getVersion())
159+
.pipeline(pipeline)
160+
.buildClient();
161+
162+
return new DocumentAnalysisClient(formRecognizerAPI);
142163
}
143164

144165
/**
@@ -174,6 +195,18 @@ public DocumentAnalysisAsyncClient buildAsyncClient() {
174195
final DocumentAnalysisServiceVersion serviceVersion =
175196
version != null ? version : DocumentAnalysisServiceVersion.getLatest();
176197

198+
HttpPipeline pipeline = getHttpPipeline(buildConfiguration);
199+
200+
final FormRecognizerClientImpl formRecognizerAPI = new FormRecognizerClientImplBuilder()
201+
.endpoint(endpoint)
202+
.apiVersion(serviceVersion.getVersion())
203+
.pipeline(pipeline)
204+
.buildClient();
205+
206+
return new DocumentAnalysisAsyncClient(formRecognizerAPI, serviceVersion);
207+
}
208+
209+
private HttpPipeline getHttpPipeline(Configuration buildConfiguration) {
177210
HttpPipeline pipeline = httpPipeline;
178211
// Create a default Pipeline if it is not given
179212
if (pipeline == null) {
@@ -190,14 +223,7 @@ public DocumentAnalysisAsyncClient buildAsyncClient() {
190223
perRetryPolicies,
191224
httpClient);
192225
}
193-
194-
final FormRecognizerClientImpl formRecognizerAPI = new FormRecognizerClientImplBuilder()
195-
.endpoint(endpoint)
196-
.apiVersion(serviceVersion.getVersion())
197-
.pipeline(pipeline)
198-
.buildClient();
199-
200-
return new DocumentAnalysisAsyncClient(formRecognizerAPI, serviceVersion);
226+
return pipeline;
201227
}
202228

203229
/**

0 commit comments

Comments
 (0)