1
votes

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.

1
It's possible that github.com/googleapis/google-cloud-java/issues/6209 is relevant here - could you try updating your google-auth-library dependency to version 0.17.1?Jon Skeet
@Jon Skeet: Thanks. But that exact version of the dependency is already in my pom.xml.applix tech
Rats. In that case, I'm afraid I don't have any ideas :(Jon Skeet
Can someone please help? If there is information missing in my post, let me know and I will make it available immediately.applix tech
One way you could help to potentially get this question answered more quickly would be to include a minimal reproducible example. I would suggest creating a console application using a service account. I suspect you could show that complete console app within 30 lines of code (probably less) - which would make it easier for people to reproduce it themselves. I've asked internally for one of my colleagues who works on the Java libraries to see if they can help you further.Jon Skeet

1 Answers

0
votes

This appears to be only an authentication issue and looks unrelated to the Vision API. The authentication error happens during the Vision API call because credentials are lazy-loaded/refreshed right before an API call is made.

It also appears that you are deploying your application to App Engine. If so, any environment variable (like GOOGLE_APPLICATION_CREDENTIALS) that is set locally, will not have any effect. You may want to check out the documentation on Granting your app access to Cloud services for App Engine. The most straight-forward method for granting access is to grant permissions to the default App Engine Service Account.