Package org.jboss.arquillian.container.appengine.tools

Source Code of org.jboss.arquillian.container.appengine.tools.AppEngineToolsContainer$DeployUpdateListener$MessageHeaders

/*
* JBoss, Home of Professional Open Source
* Copyright 2013 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.arquillian.container.appengine.tools;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.appengine.repackaged.com.google.api.client.auth.oauth2.Credential;
import com.google.appengine.tools.admin.AppAdmin;
import com.google.appengine.tools.admin.AppAdminFactory;
import com.google.appengine.tools.admin.Application;
import com.google.appengine.tools.admin.GenericApplication;
import com.google.appengine.tools.admin.OAuth2Native;
import com.google.appengine.tools.admin.UpdateFailureEvent;
import com.google.appengine.tools.admin.UpdateListener;
import com.google.appengine.tools.admin.UpdateProgressEvent;
import com.google.appengine.tools.admin.UpdateSuccessEvent;
import com.google.appengine.tools.info.SdkInfo;
import com.google.appengine.tools.info.UpdateCheck;
import com.google.apphosting.utils.config.AppEngineConfigException;
import org.jboss.arquillian.container.common.AppEngineCommonContainer;
import org.jboss.arquillian.container.common.ParseUtils;
import org.jboss.arquillian.container.spi.ConfigurationException;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.descriptor.api.Descriptors;
import org.jboss.shrinkwrap.descriptor.api.application5.ApplicationDescriptor;
import org.jboss.shrinkwrap.descriptor.api.application5.ModuleType;
import org.xml.sax.SAXParseException;

/**
* Tools AppEngine container. <p/> Code taken from "http://code.google.com/p/google-plugin-for-eclipse/source/browse/trunk/plugins/com.google.appengine.eclipse.core/proxy_src/com/google/appengine/eclipse/core/proxy/AppEngineBridgeImpl.java?r=2"
* with permission from GAE team.
*
* @author GAE Team
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
*/
public class AppEngineToolsContainer extends AppEngineCommonContainer<AppEngineToolsConfiguration> {
    private AppEngineToolsConfiguration configuration;
    private Map<String, String> modules = new LinkedHashMap<String, String>();

    public Class<AppEngineToolsConfiguration> getConfigurationClass() {
        return AppEngineToolsConfiguration.class;
    }

    public void setup(AppEngineToolsConfiguration configuration) {
        final String sdkDir = configuration.getSdkDir();
        if (sdkDir == null)
            throw new ConfigurationException("AppEngine SDK root is null.");

        if (configuration.getOauth2token() == null) {
            final String userId = configuration.getUserId();
            if (userId == null)
                throw new ConfigurationException("Null userId.");

            final String password = configuration.getPassword();
            if (password == null)
                throw new ConfigurationException("Null password.");
        }

        SdkInfo.setSdkRoot(new File(sdkDir));

        this.configuration = configuration;
    }

    protected File export(Archive<?> archive) throws Exception {
        if (archive instanceof WebArchive) {
            modules.put(parseModule(WebArchive.class.cast(archive)), ""); // we have plain .war, hence default module
            return super.export(archive);
        } else if (archive instanceof EnterpriseArchive) {
            return rearrangeEar(EnterpriseArchive.class.cast(archive));
        } else {
            throw new IllegalArgumentException("Can only handle .war or .ear deployments: " + archive);
        }
    }

    protected File rearrangeEar(EnterpriseArchive ear) {
        final File root = getTempRoot();

        final Node appXml = ear.get(ParseUtils.APPLICATION_XML);
        if (appXml != null) {
            InputStream stream = appXml.getAsset().openStream();
            try {
                ApplicationDescriptor ad = Descriptors.importAs(ApplicationDescriptor.class).fromStream(stream);

                List<JavaArchive> libs = new ArrayList<JavaArchive>();
                String libDir = ad.getLibraryDirectory();
                if (libDir != null) {
                    libDir = "lib"; // default?
                }
                Node lib = ear.get(libDir);
                if (lib != null) {
                    // defensive copy
                    final Set<Node> children = new HashSet<Node>(lib.getChildren());
                    for (Node child : children) {
                        if (child.getPath().get().endsWith(".jar")) {
                            JavaArchive jar = ear.getAsType(JavaArchive.class, child.getPath());
                            libs.add(jar);
                        }
                    }
                }

                List<ModuleType<ApplicationDescriptor>> allModules = ad.getAllModule();
                for (ModuleType<ApplicationDescriptor> mt : allModules) {
                    String uri = mt.getOrCreateWeb().getWebUri();
                    if (uri != null) {
                        WebArchive war = ear.getAsType(WebArchive.class, uri);
                        handleWar(root, libs, war, uri);
                    } else {
                        mt.removeWeb();
                    }
                }
            } finally {
                safeClose(stream);
            }
        }

        return root;
    }

    private void handleWar(File root, List<JavaArchive> libs, WebArchive war, String uri) {
        try {
            String module = parseModule(war);
            if (modules.put(module, uri) != null) {
                throw new IllegalArgumentException(String.format("Duplicate module %s in %s", module, modules));
            } else {
                log.info(String.format("Found %s module in web archive %s", module, war.getName()));
            }

            WebArchive copy = ShrinkWrap.create(WebArchive.class, war.getName());
            copy.merge(war);
            copy.addAsLibraries(libs);
            export(copy, root);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    protected static String parseModule(WebArchive war) throws Exception {
        final Node awXml = war.get(ParseUtils.APPENGINE_WEB_XML);
        if (awXml == null) {
            throw new IllegalStateException("Missing appengine-web.xml: " + war);
        }
        final Map<String, String> results = ParseUtils.parseTokens(awXml, ParseUtils.MODULE);
        String module = results.get(ParseUtils.MODULE);
        if (module == null) {
            module = DEFAULT;
        }
        return module;
    }

    protected ProtocolMetaData doDeploy(Archive<?> archive) throws DeploymentException {
        try {
            return doDeployInternal(archive);
        } finally {
            modules.clear();
        }
    }

    protected ProtocolMetaData doDeployInternal(Archive<?> archive) throws DeploymentException {
        if (configuration.isUpdateCheck()) {
            UpdateCheck updateCheck = new UpdateCheck(SdkInfo.getDefaultServer());
            if (updateCheck.allowedToCheckForUpdates()) {
                updateCheck.maybePrintNagScreen(new PrintStream(System.out, true));
            }
        }

        String appId = configuration.getAppId();
        final String module = configuration.getModule();

        if (archive instanceof WebArchive) {
            Application app = readApplication("");

            if (appId != null) {
                app.getAppEngineWebXml().setAppId(appId);
            } else if (appId == null) {
                appId = app.getAppEngineWebXml().getAppId();
            }

            if (module != null) {
                app.getAppEngineWebXml().setModule(module);
            }

            handleApp(app);
        } else if (archive instanceof EnterpriseArchive) {
            EnterpriseArchive ear = EnterpriseArchive.class.cast(archive);
            if (appId == null) {
                Node aaXml = ear.get(ParseUtils.APPENGINE_APPLICATION_XML);
                if (aaXml == null) {
                    throw new IllegalArgumentException("Missing appengine-application.xml: " + ear);
                }
                try {
                    Map<String, String> results = ParseUtils.parseTokens(aaXml, ParseUtils.APPLICATION);
                    appId = results.get(ParseUtils.APPLICATION);
                } catch (Exception e) {
                    throw new DeploymentException(e.getMessage());
                }
            }

            if (module != null) {
                String war = modules.get(module);
                if (war == null) {
                    throw new IllegalArgumentException(String.format("No such module %s in %s", module, modules));
                }

                Application app = readApplication(war);

                if (appId != null) {
                    app.getAppEngineWebXml().setAppId(appId);
                }

                app.getAppEngineWebXml().setModule(module);

                handleApp(app);
            } else {
                for (Map.Entry<String, String> entry : modules.entrySet()) {
                    Application app = readApplication(entry.getValue());

                    if (appId != null) {
                        app.getAppEngineWebXml().setAppId(appId);
                    }

                    handleApp(app);
                }
            }
        }

        String server = configuration.getServer();
        if (server == null) {
            server = "appspot.com";
        }
        String host = appId + "." + server;

        if (module == null) {
            String[] appModules = modules.keySet().toArray(new String[modules.size()]);
            return getProtocolMetaData(host, configuration.getPort(), appModules);
        } else {
            return getProtocolMetaData(host, configuration.getPort(), module);
        }
    }

    protected void handleApp(Application app) throws DeploymentException {
        try {
            final AppAdmin appAdmin = createAppAdmin(app);

            final DeployUpdateListener listener = new DeployUpdateListener(
                this,
                new PrintWriter(System.out, true),
                new PrintWriter(System.err, true)
            );

            getExecutor().execute(new Runnable() {
                public void run() {
                    appAdmin.update(listener);
                }
            });

            Status status;
            synchronized (this) {
                do {
                    wait(configuration.getStartupTimeout());
                    status = listener.getStatus();
                } while (status == null); // guard against spurious wakeup
            }

            if (status != Status.OK) {
                throw new DeploymentException("Cannot deploy via GAE tools: " + status);
            }
        } catch (DeploymentException e) {
            throw e;
        } catch (AppEngineConfigException e) {
            if (e.getCause() instanceof SAXParseException) {
                String msg = e.getCause().getMessage();

                // have to check what the message says to distinguish a file-not-found
                // problem from some other xml problem.
                if (msg.contains("Failed to read schema document") && msg.contains("backends.xsd")) {
                    throw new IllegalArgumentException("Deploying a project with backends requires App Engine SDK 1.5.0 or greater.", e);
                } else {
                    throw e;
                }
            } else {
                throw e;
            }
        } catch (Exception e) {
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
            throw new DeploymentException("Cannot deploy via GAE tools.", e);
        }
    }

    protected Executor getExecutor() {
        return Executors.newSingleThreadExecutor();
    }

    protected Application readApplication(String path) {
        try {
            File app = getAppLocation();
            if (path != null && path.trim().length() > 0) {
                app = new File(app, path);
            }
            return Application.readApplication(app.getCanonicalPath());
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    // Based on com.google.appengine.tools.admin.AppCfg.authorizedOauth2()
    private String getOAuthToken() {
        String runOauth2Msg = "Create ${HOME}/.appcfg_oauth2_tokens by running $appcfg.sh --oauth2 update YOUR-WAR-DIR";
        String userDir = System.getProperty("user.home");
        File tokenFile = new File(userDir, ".appcfg_oauth2_tokens_java");

        if (!tokenFile.exists()) {
            throw new ConfigurationException(runOauth2Msg);
        }

        boolean useCookies = true// use ${HOME}/.appcfg_oauth2_tokens_java
        String oauth2ClientId = null;
        String oauth2ClientSecret = null;
        String oauth2RefreshToken = null;
        OAuth2Native client = new OAuth2Native(useCookies, oauth2ClientId,
            oauth2ClientSecret, oauth2RefreshToken);
        Credential credential = client.authorize();

        if (credential == null || credential.getAccessToken() == null) {
            String errMsg = "Tokens expired? " + runOauth2Msg;
            throw new ConfigurationException(errMsg);
        }
        return credential.getAccessToken();
    }

    AppAdmin createAppAdmin(GenericApplication app) throws IOException {
        AppAdminFactory appAdminFactory = new AppAdminFactory();

        /**
         if (options.getJavaExecutableOSPath() != null) {
         appAdminFactory.setJavaExecutable(new File(options.getJavaExecutableOSPath()));
         }

         if (options.getJavaCompilerExecutableOSPath() != null) {
         appAdminFactory.setJavaCompiler(new File(options.getJavaCompilerExecutableOSPath()));
         }
         */

        final AppAdminFactory.ConnectOptions appEngineConnectOptions = new AppAdminFactory.ConnectOptions();
        // APPENGINE_SERVER is server to upload the app.
        //  The default is appspot.com, which is used for both upload and test container.
        String appengineServer = System.getenv("APPENGINE_SERVER");
        if (appengineServer != null) {
            appEngineConnectOptions.setServer(appengineServer);
        }

        // User/Password
        appEngineConnectOptions.setUserId(configuration.getUserId());
        // TODO -- better prompt?
        appEngineConnectOptions.setPasswordPrompt(new AppAdminFactory.PasswordPrompt() {
            public String getPassword() {
                return configuration.getPassword();
            }
        });

        // OAuth2
        String oauthToken = null;
        String configOauthToken = configuration.getOauth2token(); // -Dappengine.oauth2token=
        if (configOauthToken != null) {
            if (configOauthToken.trim().equals("")) {  // if blank token, get it from cookie.
                oauthToken = getOAuthToken();
            } else {
                oauthToken = configOauthToken;
            }
        }
        // if oauthToken is null, username/pw will be used.
        appEngineConnectOptions.setOauthToken(oauthToken);


        PrintWriter errorWriter = new PrintWriter(System.err, true);

        return appAdminFactory.createAppAdmin(appEngineConnectOptions, app, errorWriter);
    }

    private static final class DeployUpdateListener implements UpdateListener {
        private static final Logger log = Logger.getLogger(DeployUpdateListener.class.getName());

        /**
         * Class for getting headers representing different stages of deployments bassed on console
         * messages from the gae sdk.
         */
        private static class MessageHeaders {

            // Headers should go in the order specified in this array.
            private static final PrefixHeaderPair[] prefixHeaderPairs = new PrefixHeaderPair[]{
                new PrefixHeaderPair("Preparing to deploy", null, "Created staging directory", "Scanning files on local disk"),
                new PrefixHeaderPair("Deploying", null, "Uploading"),
                new PrefixHeaderPair("Verifying availability", "Verifying availability of", "Will check again in 1 seconds."),
                new PrefixHeaderPair("Updating datastore", null, "Uploading index")};

            /*
             * The headers should go in the sequence specified in the array,
             * so keep track of which header we're currently looking for.
             */
            private int currentPrefixHeaderPair;

            PrefixHeaderPair getMessageHeader(String msg) {
                PrefixHeaderPair php = prefixHeaderPairs[currentPrefixHeaderPair];
                for (String prefix : php.msgPrefixes) {
                    if (msg.startsWith(prefix)) {
                        currentPrefixHeaderPair = (currentPrefixHeaderPair + 1) % prefixHeaderPairs.length;
                        return php;
                    }
                }
                return null;
            }
        }

        /**
         * Class for holding the different gae sdk messages that are associated with different
         * "headers", representing the stages of deployment.
         */
        private static class PrefixHeaderPair {
            // the header that should be displayed on the console
            final String header;

            // the prefixes of console messages that trigger this header
            final String[] msgPrefixes;

            // the header that should be displayed on the progress dialog, mainly for
            // displaying "verifying availability" on the console and displaying
            // "verifying availability of "backend""
            final String taskHeader;

            PrefixHeaderPair(String header, String taskHeader, String... msgPrefixes) {
                this.msgPrefixes = msgPrefixes;
                this.header = header;
                if (taskHeader == null) {
                    this.taskHeader = header;
                } else {
                    this.taskHeader = taskHeader;
                }
            }
        }

        /**
         * Attempts to reflectively call getDetails() on the event object received by the onFailure
         * or onSuccess callback. That method is only supported by App Engine SDK 1.2.1 or later. If
         * we are able to call getDetails we return the details message; otherwise we return
         * <code>null</code>.
         */
        private static String getDetailsIfSupported(Object updateEvent) {
            try {
                Method method = updateEvent.getClass().getDeclaredMethod("getDetails");
                return (String) method.invoke(updateEvent);
            } catch (NoSuchMethodException e) {
                // Expected on App Engine SDK 1.2.0; no need to log
            } catch (Exception e) {
                log.log(Level.SEVERE, e.getMessage(), e);
            }
            return null;
        }

        /**
         * Reflectively checks to see if an exception is a JspCompilationException, which is only
         * supported by App Engine SDK 1.2.1 or later.
         */
        private static boolean isJspCompilationException(Throwable ex) {
            if (ex != null) {
                try {
                    Class<?> jspCompilationExceptionClass = Class.forName("com.google.appengine.tools.admin.JspCompilationException");
                    return jspCompilationExceptionClass.isAssignableFrom(ex.getClass());
                } catch (ClassNotFoundException e) {
                    // Expected on App Engine SDK 1.2.0; no need to log
                }
            }
            return false;
        }

        private final Object waiter;
        private final PrintWriter errorWriter;
        private final PrintWriter outputWriter;

        private MessageHeaders messageHeaders;
        private int percentDone = 0;
        private Status status = null;

        private DeployUpdateListener(Object waiter, PrintWriter outputWriter, PrintWriter errorWriter) {
            this.waiter = waiter;
            this.outputWriter = outputWriter;
            this.errorWriter = errorWriter;
            this.messageHeaders = new MessageHeaders();
        }

        public Status getStatus() {
            return status;
        }

        public void onFailure(UpdateFailureEvent event) {
            // Create status object and print error message to the writer
            status = Status.ERROR;
            outputWriter.println(event.getFailureMessage());

            // Only print the details for JSP compilation errors
            if (isJspCompilationException(event.getCause())) {
                String details = getDetailsIfSupported(event);
                if (details != null) {
                    outputWriter.println(details);
                }
            }

            synchronized (waiter) {
                waiter.notify();
            }
        }

        public void onProgress(UpdateProgressEvent event) {
            // Update the progress monitor
            int worked = event.getPercentageComplete() - percentDone;
            percentDone += worked;

            String msg = event.getMessage();
            PrefixHeaderPair php = messageHeaders.getMessageHeader(msg);

            if (php != null) {
                outputWriter.println("\n" + php.header + ":");
            }
            outputWriter.println("\t" + msg);
        }

        public void onSuccess(UpdateSuccessEvent event) {
            status = Status.OK;

            percentDone = 0; // reset

            String details = getDetailsIfSupported(event);
            if (details != null) {
                // Note that unlike in onFailure, we're writing to the log file here,
                // not to the console. This is so we don't clutter our deployment
                // console with a bunch of info or warning messages from JSP
                // compilation, if we deployed successfully.
                errorWriter.println(details);
            }

            outputWriter.println("\nDeployment completed successfully");

            synchronized (waiter) {
                waiter.notify();
            }
        }

        /**
         * Println's the given string to this DeployUpdateListener's output writer.
         */
        public void println(String s) {
            outputWriter.println(s);
        }
    }
}
TOP

Related Classes of org.jboss.arquillian.container.appengine.tools.AppEngineToolsContainer$DeployUpdateListener$MessageHeaders

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.