com.amazonaws.eclipse.lambda.invoke.handler.InvokeFunctionHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.eclipse.lambda.invoke.handler.InvokeFunctionHandler.java

Source

/*
 * Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */
package com.amazonaws.eclipse.lambda.invoke.handler;

import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_NAME_END_RESULT;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_NAME_OPENED_FROM;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_CANCELED;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_FAILED;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_PROJECT_CONTEXT_MENU;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_SUCCEEDED;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_UPLOAD_BEFORE_INVOKE;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.EVENT_TYPE_INVOKE_FUNCTION_DIALOG;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.EVENT_TYPE_UPLOAD_FUNCTION_WIZARD;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.METRIC_NAME_IS_INVOKE_INPUT_MODIFIED;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.METRIC_NAME_IS_PROJECT_MODIFIED_AFTER_LAST_INVOKE;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.handlers.HandlerUtil;

import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.mobileanalytics.ToolkitAnalyticsManager;
import com.amazonaws.eclipse.lambda.LambdaPlugin;
import com.amazonaws.eclipse.lambda.invoke.ui.InvokeFunctionInputDialog;
import com.amazonaws.eclipse.lambda.project.metadata.LambdaFunctionProjectMetadata;
import com.amazonaws.eclipse.lambda.project.wizard.util.FunctionProjectUtil;
import com.amazonaws.eclipse.lambda.upload.wizard.handler.UploadFunctionToLambdaCommandHandler;
import com.amazonaws.eclipse.lambda.upload.wizard.util.FunctionJarExportHelper;
import com.amazonaws.services.lambda.AWSLambda;
import com.amazonaws.services.lambda.model.InvocationType;
import com.amazonaws.services.lambda.model.InvokeRequest;
import com.amazonaws.services.lambda.model.InvokeResult;
import com.amazonaws.services.lambda.model.LogType;
import com.amazonaws.services.lambda.model.UpdateFunctionCodeRequest;
import com.amazonaws.services.lambda.model.UpdateFunctionCodeResult;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.util.StringUtils;

public class InvokeFunctionHandler extends AbstractHandler {

    private static final String LAMBDA_CONSOLE_NAME = "AWS Lambda Console";

    public Object execute(ExecutionEvent event) throws ExecutionException {

        ISelection selection = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage().getSelection();

        if (selection instanceof IStructuredSelection) {
            IStructuredSelection structurredSelection = (IStructuredSelection) selection;
            Object firstSeleciton = structurredSelection.getFirstElement();

            IProject selectedProject = null;

            if (firstSeleciton instanceof IProject) {
                selectedProject = (IProject) firstSeleciton;
            } else if (firstSeleciton instanceof IJavaProject) {
                selectedProject = ((IJavaProject) firstSeleciton).getProject();
            } else {
                LambdaPlugin.getDefault().logInfo("Invalid selection: " + firstSeleciton + " is not a project.");
                return null;
            }

            trackInvokeDialogOpenedFromProjectContextMenu();

            try {
                invokeLambdaFunctionProject(selectedProject);
            } catch (Exception e) {
                LambdaPlugin.getDefault().reportException("Failed to launch upload function wizard.", e);
            }
        }

        return null;
    }

    public static void invokeLambdaFunctionProject(IProject project) {

        LambdaFunctionProjectMetadata md = FunctionProjectUtil.loadLambdaProjectMetadata(project);

        if (md != null && md.isValid()) {
            InvokeFunctionInputDialog inputDialog = new InvokeFunctionInputDialog(
                    Display.getCurrent().getActiveShell(), project);
            int retCode = inputDialog.open();

            if (retCode == InvokeFunctionInputDialog.INVOKE_BUTTON_ID) {
                String input = inputDialog.getInputBoxContent();

                boolean isProjectDirty = LambdaPlugin.getDefault().getProjectChangeTracker()
                        .isProjectDirty(project);
                boolean isInvokeInputModified = inputDialog.isInputBoxContentModified();

                trackIsProjectModifiedAfterLastInvoke(isProjectDirty);
                trackIsInvokeInputModified(isInvokeInputModified);

                if (isProjectDirty) {
                    invokeAfterRepeatingLastDeployment(input, project, md);
                } else {
                    invokeWithoutDeployment(input, project, md);
                }
            } else {
                trackInvokeCanceled();
            }

        } else {
            askForDeploymentFirst(project);
        }
    }

    private static void invokeAfterRepeatingLastDeployment(final String invokeInput, final IProject project,
            final LambdaFunctionProjectMetadata metadata) {
        _doInvoke(invokeInput, project, metadata, true);
    }

    private static void invokeWithoutDeployment(final String invokeInput, final IProject project,
            final LambdaFunctionProjectMetadata metadata) {
        _doInvoke(invokeInput, project, metadata, false);
    }

    private static void _doInvoke(final String invokeInput, final IProject project,
            final LambdaFunctionProjectMetadata metadata, final boolean updateFunctionCode) {

        MessageConsole console = getOrCreateLambdaConsoleIfNotExist();
        console.clearConsole();
        ConsolePlugin.getDefault().getConsoleManager().showConsoleView(console);
        final MessageConsoleStream consoleOutput = console.newMessageStream();

        final AWSLambda lambda = AwsToolkitCore.getClientFactory()
                .getLambdaClientByEndpoint(metadata.getLastDeploymentEndpoint());
        final String funcName = metadata.getLastDeploymentFunctionName();
        final String bucketName = metadata.getLastDeploymentBucketName();

        new Job("Running " + funcName + " on Lambda...") {

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    if (updateFunctionCode) {
                        updateFunctionCode(lambda, project, funcName, bucketName, consoleOutput);
                        /*
                         * clear the dirty flag so that the next invoke will not
                         * attempt to re-upload the code if no change is made to
                         * the project.
                         */
                        LambdaPlugin.getDefault().getProjectChangeTracker().markProjectAsNotDirty(project);
                    } else {
                        consoleOutput.println("Skip uploading function code since no local change is found...");
                    }
                    invokeFunction(lambda, invokeInput, funcName, consoleOutput);
                    saveInvokeInputInProjectMetadata(invokeInput, project);
                } catch (Exception e) {
                    trackInvokeFailed();
                    consoleOutput.println("==================== INVOCATION ERROR ====================");
                    consoleOutput.println(e.toString());
                }

                trackInvokeSucceeded();

                try {
                    consoleOutput.close();
                } catch (IOException e) {
                    LambdaPlugin.getDefault().warn("Failed to close console message stream.", e);
                }

                return Status.OK_STATUS;
            }
        }.schedule();
    }

    private static MessageConsole getOrCreateLambdaConsoleIfNotExist() {

        IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager();

        // Search existing consoles
        if (consoleManager.getConsoles() != null) {
            for (IConsole console : consoleManager.getConsoles()) {
                if (LAMBDA_CONSOLE_NAME.equals(console.getName()) && (console instanceof MessageConsole)) {
                    return (MessageConsole) console;
                }
            }
        }

        // If not found, create a new console
        MessageConsole newConsole = new MessageConsole(LAMBDA_CONSOLE_NAME, null);
        ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] { newConsole });
        return newConsole;
    }

    private static void updateFunctionCode(AWSLambda lambda, IProject project, String funcName, String bucketName,
            MessageConsoleStream out) throws IOException {

        out.println("Uploading function code to " + funcName + "...");

        File funcCodeFile = FunctionJarExportHelper.exportProjectToJarFile(project, false);
        String randomKeyName = UUID.randomUUID().toString();

        AmazonS3 s3 = AwsToolkitCore.getClientFactory().getS3ClientForBucket(bucketName);
        s3.putObject(bucketName, randomKeyName, funcCodeFile);

        UpdateFunctionCodeResult result = lambda.updateFunctionCode(new UpdateFunctionCodeRequest()
                .withFunctionName(funcName).withS3Bucket(bucketName).withS3Key(randomKeyName));

        // Clean up ourself after the function is created
        try {
            s3.deleteObject(bucketName, randomKeyName);
        } catch (Exception e) {
            out.println(String.format(
                    "Failed to cleanup function code in S3. " + "Please remove the object manually s3://%s/%s",
                    bucketName, randomKeyName));
        }

        out.println("Upload success. Function ARN: " + result.getFunctionArn());
    }

    private static void invokeFunction(AWSLambda lambda, String input, String funcName, MessageConsoleStream out) {

        out.println("Invoking function...");

        InvokeResult result = lambda.invoke(new InvokeRequest().withFunctionName(funcName)
                .withInvocationType(InvocationType.RequestResponse).withLogType(LogType.None).withPayload(input));

        out.println("==================== FUNCTION OUTPUT ====================");
        out.print(readPayload(result.getPayload()));
    }

    private static void saveInvokeInputInProjectMetadata(String invokeInput, IProject project) {

        // Load existing metadata so it doens't clobber other metadata fields
        LambdaFunctionProjectMetadata md = FunctionProjectUtil.loadLambdaProjectMetadata(project);
        md.setLastInvokeInput(invokeInput);

        FunctionProjectUtil.addLambdaProjectMetadata(project, md);
    }

    private static void askForDeploymentFirst(IProject project) {
        MessageDialog dialog = new MessageDialog(Display.getCurrent().getActiveShell(), "Function not uploaded yet",
                null, "You need to upload the function to Lambda before invoking it.", MessageDialog.INFORMATION,
                new String[] { "Upload now", "Cancel" }, 0);
        int result = dialog.open();

        if (result == 0) {
            trackUploadWizardOpenedBeforeFunctionInvoke();
            UploadFunctionToLambdaCommandHandler.doUploadFunctionProjectToLambda(project);
        }
    }

    private static String readPayload(ByteBuffer payload) {
        return new String(payload.array(), StringUtils.UTF8);
    }

    /*
     * Analytics
     */

    private static void trackUploadWizardOpenedBeforeFunctionInvoke() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addAttribute(ATTR_NAME_OPENED_FROM, ATTR_VALUE_UPLOAD_BEFORE_INVOKE).build());
    }

    private void trackInvokeDialogOpenedFromProjectContextMenu() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_INVOKE_FUNCTION_DIALOG)
                .addAttribute(ATTR_NAME_OPENED_FROM, ATTR_VALUE_PROJECT_CONTEXT_MENU).build());
    }

    private static void trackIsProjectModifiedAfterLastInvoke(boolean isModified) {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_INVOKE_FUNCTION_DIALOG)
                .addBooleanMetric(METRIC_NAME_IS_PROJECT_MODIFIED_AFTER_LAST_INVOKE, isModified).build());
    }

    private static void trackInvokeSucceeded() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_INVOKE_FUNCTION_DIALOG)
                .addAttribute(ATTR_NAME_END_RESULT, ATTR_VALUE_SUCCEEDED).build());
    }

    private static void trackInvokeFailed() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_INVOKE_FUNCTION_DIALOG)
                .addAttribute(ATTR_NAME_END_RESULT, ATTR_VALUE_FAILED).build());
    }

    private static void trackInvokeCanceled() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_INVOKE_FUNCTION_DIALOG)
                .addAttribute(ATTR_NAME_END_RESULT, ATTR_VALUE_CANCELED).build());
    }

    private static void trackIsInvokeInputModified(boolean isModified) {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_INVOKE_FUNCTION_DIALOG)
                .addBooleanMetric(METRIC_NAME_IS_INVOKE_INPUT_MODIFIED, isModified).build());
    }
}