I would like to provide the "Search for any text in a PDF or a TIFF file already stored in Google Cloud Storage" feature in my Google Cloud-based application. So I copied the example given (on lines 1217 through to 1280) at this link https://github.com/GoogleCloudPlatform/java-docs-samples/blob/master/vision/cloud-client/src/main/java/com/example/vision/Detect.java into my project.
Details of attempt 1
Contents of appengine-web.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>garbledappname</application>
<version>1</version>
<runtime>java8</runtime>
<threadsafe>true</threadsafe>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
<sessions-enabled>true</sessions-enabled>
<url-stream-handler>urlfetch</url-stream-handler>
</appengine-web-app>
I got this exception:
javax.servlet.ServletException: org.glassfish.jersey.server.ContainerException: java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information
So I changed the appengine-web.xml for attempt 2.....
Details of attempt 2 Contents of appengine-web.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>garbledappname</application>
<version>1</version>
<runtime>java8</runtime>
<threadsafe>true</threadsafe>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
<sessions-enabled>true</sessions-enabled>
<url-stream-handler>urlfetch</url-stream-handler>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS"
value="WEB-INF/myproject-eaa46206856e.json" />
</env-variables>
</appengine-web-app>
In short, in attempt 2, I added the "GOOGLE_APPLICATION_CREDENTIALS" env variable and pointed it to the json key file (which was created and downloaded on my PC from the Google Cloud Platform console at this link https://console.cloud.google.com/iam-admin/serviceaccounts.
For sake of completeness, I reproduce the json file contents:
{
"type": "service_account",
"project_id": "garbledappname",
"private_key_id": "garbled_key_id",
"private_key": "-----BEGIN PRIVATE KEY-----\garbled_private_key\n-----END PRIVATE KEY-----\n",
"client_email": "r\[email protected]",
"client_id": "108443712409676506799",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/myOrg-demo-vison%40garbled.iam.gserviceaccount.com"
}
After changing the appengine-web.xml as above, building, deploying and executing the app, I got this exception:
org.eclipse.jetty.servlet.ServletHandler doHandle: (ServletHandler.java:624)
javax.servlet.ServletException: com.google.api.gax.rpc.UnauthenticatedException: io.grpc.StatusRuntimeException: UNAUTHENTICATED: Failed computing credential metadata
In my code, this is the line reported in the trace above:
BatchAnnotateFilesResponse response = imageAnnotatorClient.batchAnnotateFiles(request);
This is the entire method:
setQualifiedFiles = mapOfQualifiedReportFiles.keySet();
itrOverSetOfQualifiedFiles = setQualifiedFiles.iterator();
while(itrOverSetOfQualifiedFiles.hasNext()) {
try (ImageAnnotatorClient imageAnnotatorClient = ImageAnnotatorClient.create()) {
qualifiedFileName = itrOverSetOfQualifiedFiles.next();
fileNameOnCloud = mapOfQualifiedReportFiles.get(qualifiedFileName);
gcsFileName = new GcsFilename(bucketNameExtVendorReports, fileNameOnCloud);
gcsPath = "gs://" + bucketNameExtVendorReports + "/" + fileNameOnCloud;
logger.info("gcsPath = " + gcsPath);
GcsSource gcsSource = GcsSource.newBuilder().setUri(gcsPath).build();
InputConfig inputConfig = InputConfig.newBuilder().setGcsSource(gcsSource).build();
Feature.Type type = Feature.Type.DOCUMENT_TEXT_DETECTION;
Feature featuresElement = Feature.newBuilder().setType(type).build();
List<Feature> features = Arrays.asList(featuresElement);
int pagesElement = 1;
int pagesElement2 = 2;
List<Integer> pages = Arrays.asList(pagesElement, pagesElement2);
AnnotateFileRequest requestsElement = AnnotateFileRequest.newBuilder()
.setInputConfig(inputConfig)
.addAllFeatures(features)
.addAllPages(pages)
.build();
List<AnnotateFileRequest> requests = Arrays.asList(requestsElement);
BatchAnnotateFilesRequest request = BatchAnnotateFilesRequest.newBuilder().addAllRequests(requests).build();
BatchAnnotateFilesResponse response = imageAnnotatorClient.batchAnnotateFiles(request);
for (AnnotateImageResponse imageResponse :
response.getResponsesList().get(0).getResponsesList()) {
System.out.printf("Full text: %s\n", imageResponse.getFullTextAnnotation().getText());
for (Page page : imageResponse.getFullTextAnnotation().getPagesList()) {
for (Block block : page.getBlocksList()) {
System.out.printf("\nBlock confidence: %s\n", block.getConfidence());
for (Paragraph par : block.getParagraphsList()) {
System.out.printf("\tParagraph confidence: %s\n", par.getConfidence());
for (Word word : par.getWordsList()) {
System.out.printf("\t\tWord confidence: %s\n", word.getConfidence());
for (Symbol symbol : word.getSymbolsList()) {
System.out.printf("\t\t\tSymbol: %s, (confidence: %s)\n",
symbol.getText(), symbol.getConfidence());
}
}
}
}
}
}
}
}
I ( believe that I) have followed all the instructions for creating a Service Account. I suspect that the Service Account thus created is missing a role that may be mandatory for accessing the Google Cloud Vision API on the cloud.
Can someone help me, please?
The following is my attempt at presenting a "Minimal, Reproducible Example" as suggested by Jon Skeet.
Client:
package com.myOrg.myApp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import org.codehaus.jackson.map.ObjectMapper;
import com.myOrg.myApp.entities.Patient;
import com.myOrg.myApp.entities.returntypes.ServerResponse;
import com.google.gson.Gson;
public class VisionAPIClient {
public VisionAPIClient() {
}
public static void main(String[] args) {
VisionAPIClient visionAPIClient = null;
visionAPIClient = new VisionAPIClient();
visionAPIClient.search(args[0], args[1], args[2]);
}
private void search(String paramIPAddress, String paramSearchString, String paramBucketNameOnGCS) {
BufferedReader bfr = null;
int iValues = 0;
int iResponseCode = 0;
Patient patient = null;
ServerResponse response = null;
String retVal = null;
String urlToVisionAPIServer = null;
URL url = null;
URLConnection urlConnection = null;
try {
urlToVisionAPIServer = paramIPAddress + "/visionAPIServer/searchTextWithinFiles?queryString=" + new Gson().toJson(paramSearchString + "@@@" + paramBucketNameOnGCS);
url = new URL(urlToVisionAPIServer);
urlConnection = url.openConnection();
bfr = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
retVal = bfr.readLine();
response = new ObjectMapper().readValue(retVal, ServerResponse.class);
iValues = response.getValues().size();
iResponseCode = response.getResponseCode();
if(iResponseCode == 200) {
for(int i=0 ; i<iValues ; i++) {
System.out.println("contents = " + new ObjectMapper().readValue((String)response.getValues().get(i), String.class));
}
}
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
Server:
package com.myOrg.myApp.serverApp;
import java.util.Arrays;
import java.util.List;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.google.appengine.tools.cloudstorage.GcsService;
import com.google.appengine.tools.cloudstorage.GcsServiceFactory;
import com.google.appengine.tools.cloudstorage.ListOptions;
import com.google.appengine.tools.cloudstorage.ListResult;
import com.google.appengine.tools.cloudstorage.RetryParams;
import com.google.cloud.vision.v1.AnnotateFileRequest;
import com.google.cloud.vision.v1.AnnotateImageResponse;
import com.google.cloud.vision.v1.BatchAnnotateFilesRequest;
import com.google.cloud.vision.v1.BatchAnnotateFilesResponse;
import com.google.cloud.vision.v1.Block;
import com.google.cloud.vision.v1.Feature;
import com.google.cloud.vision.v1.GcsSource;
import com.google.cloud.vision.v1.ImageAnnotatorClient;
import com.google.cloud.vision.v1.InputConfig;
import com.google.cloud.vision.v1.Page;
import com.google.cloud.vision.v1.Paragraph;
import com.google.cloud.vision.v1.Symbol;
import com.google.cloud.vision.v1.Word;
@Path("/visionAPIServer")
public class VisionAPIServer {
@Context
private HttpServletRequest request = null;
private static Logger logger = Logger.getLogger("visionAPIServer");
@GET
@Path("/searchTextWithinFiles")
@Produces(MediaType.TEXT_PLAIN)
public synchronized Response searchTextWithinFiles(@QueryParam("queryString")String paramQueryString)
throws Exception {
logger.info("//////////////////////////////////////////////////////////////////////////////////////");
logger.info("begin new method: " + new Object(){}.getClass().getEnclosingMethod().getName());
logger.info("\t\tparamQueryString = " + paramQueryString);
logger.info("//////////////////////////////////////////////////////////////////////////////////////");
AnnotateFileRequest requestsElement = null;
BatchAnnotateFilesRequest request = null;
BatchAnnotateFilesResponse response = null;
Feature featuresElement = null;
Feature.Type type = null;
GcsService gcsService = null;
GcsSource gcsSource = null;
InputConfig inputConfig = null;
int pagesElement = 0;
int pagesElement2 = 0;
List<AnnotateFileRequest> requests = null;
List<Feature> features = null;
List<Integer> pages = null;
ListResult listResult = null;
String paramBucketName = null;
String paramSearchText = null;
String jsonReturnValue = null;
String fileNameOnCloud = null;
String gcsPath = null;
String[] arrOfValues = null;
arrOfValues = (new ObjectMapper().readValue(paramQueryString, String.class)).split("@@@");
if(arrOfValues.length == 2) {
paramSearchText = arrOfValues[0].trim();
paramBucketName = arrOfValues[1].trim();
if(null != paramBucketName && !("".equals(paramBucketName)) && (null != paramSearchText && !("".equals(paramSearchText)))) {
gcsService = GcsServiceFactory.createGcsService ( new RetryParams.Builder()
.initialRetryDelayMillis(10)
.retryMaxAttempts(10)
.totalRetryPeriodMillis(15000)
.build());
listResult = gcsService.list(paramBucketName, ListOptions.DEFAULT);
if(null != listResult) {
while(listResult.hasNext()) {
try (ImageAnnotatorClient imageAnnotatorClient = ImageAnnotatorClient.create()) {
fileNameOnCloud = listResult.next().getName();
gcsPath = "gs://" + paramBucketName + "/" + fileNameOnCloud;
gcsSource = GcsSource.newBuilder().setUri(gcsPath).build();
inputConfig = InputConfig.newBuilder().setGcsSource(gcsSource).build();
type = Feature.Type.DOCUMENT_TEXT_DETECTION;
featuresElement = Feature.newBuilder().setType(type).build();
features = Arrays.asList(featuresElement);
pagesElement = 1;
pagesElement2 = 2;
pages = Arrays.asList(pagesElement, pagesElement2);
requestsElement = AnnotateFileRequest.newBuilder()
.setInputConfig(inputConfig)
.addAllFeatures(features)
.addAllPages(pages)
.build();
requests = Arrays.asList(requestsElement);
request = BatchAnnotateFilesRequest.newBuilder().addAllRequests(requests).build();
response = imageAnnotatorClient.batchAnnotateFiles(request);
for (AnnotateImageResponse imageResponse :
response.getResponsesList().get(0).getResponsesList()) {
jsonReturnValue = imageResponse.getFullTextAnnotation().getText();
System.out.printf("Full text: %s\n", imageResponse.getFullTextAnnotation().getText());
for (Page page : imageResponse.getFullTextAnnotation().getPagesList()) {
for (Block block : page.getBlocksList()) {
System.out.printf("\nBlock confidence: %s\n", block.getConfidence());
for (Paragraph par : block.getParagraphsList()) {
System.out.printf("\tParagraph confidence: %s\n", par.getConfidence());
for (Word word : par.getWordsList()) {
System.out.printf("\t\tWord confidence: %s\n", word.getConfidence());
for (Symbol symbol : word.getSymbolsList()) {
System.out.printf("\t\t\tSymbol: %s, (confidence: %s)\n",symbol.getText(), symbol.getConfidence());
}
}
}
}
}
}
}
}
}
}
}
return Response.ok(jsonReturnValue)
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS")
.header("Access-Control-Allow-Headers", "Content-Type")
.header("Content-Disposition", "inline")
.build();
}
}
The appengine-web.xml file:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>myApp</application>
<version>1</version>
<runtime>java8</runtime>
<threadsafe>true</threadsafe>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
<sessions-enabled>true</sessions-enabled>
<url-stream-handler>urlfetch</url-stream-handler>
<env-variables>
<env-var name="GOOGLE_APPLICATION_CREDENTIALS"
value="WEB-INF/myapp-eba46306896e.json" />
</env-variables>
</appengine-web-app>
Re: my attempt at presenting a "Minimal, Reproducible Example"..... As stated here, and I quote...The Vision API can detect and transcribe text from PDF and TIFF files stored in Cloud Storage...end quote.
So the client part of the "mre" posted earlier makes an https call to the app on Google App Engine. While doing so, it passes the text to be searched and the name of bucket(on Google Cloud Storage) that contains the files to be searched in.
Added on 26 Dec 2019 after following Jeff Ching's suggestions: There are two links there. I had followed the instructions therein, earlier too and followed them all over again after Jeff's post.
The offending line of code remains:
response = imageAnnotatorClient.batchAnnotateFiles(request);
So I surrounded that line with a try catch cluase and the resulting stack trace revealed this:
com.google.api.gax.rpc.UnauthenticatedException: io.grpc.StatusRuntimeException: UNAUTHENTICATED: Failed computing credential metadata
Caused by: com.google.apphosting.api.ApiProxy$CallNotFoundException: Can't make API call urlfetch.Fetch in a thread that is neither the original request thread nor a thread created by ThreadManager
Any suggestions on how to resolve this? A google search brings up a few posts which where others have faced this issue (Caused by: com.goo......) but there seems to be no solution.
Any help appreciated.