org.nuxeo.vision.core.service.VisionImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.vision.core.service.VisionImpl.java

Source

/*
 * (C) Copyright 2015-2016 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Michael Vachette
 */
package org.nuxeo.vision.core.service;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.vision.v1.VisionScopes;
import com.google.api.services.vision.v1.model.*;
import com.google.common.collect.ImmutableList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.runtime.model.ComponentContext;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.DefaultComponent;
import org.nuxeo.vision.core.google.GoogleVisionDescriptor;
import org.nuxeo.vision.core.google.GoogleVisionResponse;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;

public class VisionImpl extends DefaultComponent implements Vision {

    private static final Log log = LogFactory.getLog(VisionImpl.class);

    private volatile com.google.api.services.vision.v1.Vision visionClient;

    protected static final String CONFIG_EXT_POINT = "configuration";

    protected static final String GOOGLE_EXT_POINT = "google";

    protected static final long _4MB = 4194304;

    protected static final long _8MB = 8388608;

    protected static final int MAX_BLOB_PER_REQUEST = 16;

    protected VisionDescriptor config = null;

    protected GoogleVisionDescriptor googleConfig = null;

    /**
     * Component activated notification. Called when the component is activated.
     * All component dependencies are resolved at that moment. Use this method
     * to initialize the component.
     *
     * @param context the component context.
     */
    @Override
    public void activate(ComponentContext context) {
        super.activate(context);
    }

    /**
     * Component deactivated notification. Called before a component is
     * unregistered. Use this method to do cleanup if any and free any resources
     * held by the component.
     *
     * @param context the component context.
     */
    @Override
    public void deactivate(ComponentContext context) {
        super.deactivate(context);
    }

    /**
     * Application started notification. Called after the application started.
     * You can do here any initialization that requires a working application
     * (all resolved bundles and components are active at that moment)
     *
     * @param context the component context. Use it to get the current bundle
     *            context
     * @throws Exception
     */
    @Override
    public void applicationStarted(ComponentContext context) {
    }

    @Override
    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
        if (CONFIG_EXT_POINT.equals(extensionPoint)) {
            config = (VisionDescriptor) contribution;
        } else if (GOOGLE_EXT_POINT.equals(extensionPoint)) {
            googleConfig = (GoogleVisionDescriptor) contribution;
        }
    }

    @Override
    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
        // Logic to do when unregistering any contribution
    }

    private com.google.api.services.vision.v1.Vision getVisionService()
            throws IOException, GeneralSecurityException {
        // thread safe lazy initialization of the google vision client
        // see https://en.wikipedia.org/wiki/Double-checked_locking
        com.google.api.services.vision.v1.Vision result = visionClient;
        if (result == null) {
            synchronized (this) {
                result = visionClient;
                if (result == null) {
                    GoogleCredential credential = null;
                    if (usesServiceAccount()) {
                        File file = new File(googleConfig.getCredentialFilePath());
                        credential = GoogleCredential.fromStream(new FileInputStream(file))
                                .createScoped(VisionScopes.all());
                    }
                    JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
                    result = visionClient = new com.google.api.services.vision.v1.Vision.Builder(
                            GoogleNetHttpTransport.newTrustedTransport(), jsonFactory, credential)
                                    .setApplicationName(googleConfig.getAppName()).build();
                }
            }
        }
        return result;
    }

    @Override
    public VisionResponse execute(Blob blob, List<VisionFeature> features, int maxResults)
            throws IOException, GeneralSecurityException, IllegalStateException {

        if (blob == null) {
            throw new IllegalArgumentException("Input Blob cannot be null");
        } else if (features == null || features.size() == 0) {
            throw new IllegalArgumentException("The feature list cannot be empty or null");
        }

        List<VisionResponse> results = execute(ImmutableList.of(blob), features, maxResults);
        if (results.size() > 0) {
            return results.get(0);
        } else {
            throw new NuxeoException("Google vision returned empty results for " + blob.getFilename());
        }
    }

    @Override
    public List<VisionResponse> execute(List<Blob> blobs, List<VisionFeature> features, int maxResults)
            throws IOException, GeneralSecurityException, IllegalStateException {

        if (blobs == null || blobs.size() == 0) {
            throw new IllegalArgumentException("Input Blob list cannot be null or empty");
        } else if (!checkBlobs(blobs)) {
            throw new IllegalArgumentException("Too many blobs or size exceeds the API limit");
        } else if (features == null || features.size() == 0) {
            throw new IllegalArgumentException("The feature list cannot be empty or null");
        }

        // build list of requested features
        List<Feature> requestFeatures = buildFeatureList(features, maxResults);

        // build list of request
        List<AnnotateImageRequest> requests = buildRequestList(blobs, requestFeatures);

        com.google.api.services.vision.v1.Vision.Images.Annotate annotate;
        annotate = getVisionService().images().annotate(new BatchAnnotateImagesRequest().setRequests(requests));

        if (!usesServiceAccount() && usesApiKey()) {
            annotate.setKey(googleConfig.getApiKey());
        }

        // Due to a bug: requests to Vision API containing large images fail
        // when GZipped.
        annotate.setDisableGZipContent(true);

        // execute request
        BatchAnnotateImagesResponse batchResponse;
        batchResponse = annotate.execute();

        // check response is not empty
        if (batchResponse.getResponses() == null) {
            throw new IllegalStateException("Google Vision returned an empty response");
        }

        List<AnnotateImageResponse> responses = batchResponse.getResponses();
        List<VisionResponse> output = new ArrayList<>();
        for (AnnotateImageResponse response : responses) {
            output.add(new GoogleVisionResponse(response));
        }
        return output;
    }

    @Override
    public String getPictureMapperChainName() {
        return config.getPictureMapperChainName();
    }

    @Override
    public String getVideoMapperChainName() {
        return config.getVideoMapperChainName();
    }

    protected List<Feature> buildFeatureList(List<VisionFeature> features, int maxResults) {

        List<Feature> requestFeatures = new ArrayList<>();
        for (VisionFeature feature : features) {
            requestFeatures.add(new Feature().setType(feature.toString()).setMaxResults(maxResults));
        }
        return requestFeatures;
    }

    protected List<AnnotateImageRequest> buildRequestList(List<Blob> blobs, List<Feature> features)
            throws IOException {

        List<AnnotateImageRequest> requests = new ArrayList<>();
        for (Blob blob : blobs) {
            AnnotateImageRequest request = new AnnotateImageRequest()
                    .setImage(new Image().encodeContent(blob.getByteArray())).setFeatures(features);
            requests.add(request);
        }
        return requests;
    }

    protected boolean checkBlobs(List<Blob> blobs) throws IOException {
        if (blobs.size() > MAX_BLOB_PER_REQUEST) {
            return false;
        }
        long totalSize = 0;
        for (Blob blob : blobs) {
            long size = blob.getLength();
            if (size <= 0) {
                throw new IOException("Could not read the blob size");
            }
            if (size > _4MB) {
                return false;
            }
            totalSize += size;
            if (totalSize > _8MB) {
                return false;
            }
        }
        return true;
    }

    protected boolean usesServiceAccount() {
        String path = googleConfig.getCredentialFilePath();
        return path != null && path.length() > 0;
    }

    protected boolean usesApiKey() {
        String key = googleConfig.getApiKey();
        return key != null && key.length() > 0;
    }

}