com.amazonaws.eclipse.elasticbeanstalk.webproject.CreateNewAwsJavaWebProjectRunnable.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.eclipse.elasticbeanstalk.webproject.CreateNewAwsJavaWebProjectRunnable.java

Source

/*
 * Copyright 2010-2012 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.
 */
/*
 * Copyright 2010-2012 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.elasticbeanstalk.webproject;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jst.j2ee.classpathdep.ClasspathDependencyUtil;
import org.eclipse.jst.j2ee.classpathdep.UpdateClasspathAttributeUtil;
import org.eclipse.jst.j2ee.project.facet.IJ2EEFacetConstants;
import org.eclipse.jst.j2ee.web.project.facet.IWebFacetInstallDataModelProperties;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.datamodel.properties.IFacetDataModelProperties;
import org.eclipse.wst.common.componentcore.datamodel.properties.IFacetProjectCreationDataModelProperties;
import org.eclipse.wst.common.componentcore.datamodel.properties.IFacetProjectCreationDataModelProperties.FacetDataModelMap;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualFolder;
import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory;
import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
import org.eclipse.wst.common.frameworks.datamodel.IDataModelOperation;
import org.eclipse.wst.common.project.facet.core.runtime.RuntimeManager;
import org.eclipse.wst.server.core.IRuntime;
import org.eclipse.wst.server.core.IRuntimeType;
import org.eclipse.wst.server.core.IRuntimeWorkingCopy;
import org.eclipse.wst.server.core.ServerCore;
import org.osgi.framework.Bundle;

import com.amazonaws.eclipse.core.AccountInfo;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.elasticbeanstalk.ElasticBeanstalkPlugin;
import com.amazonaws.eclipse.sdk.ui.JavaSdkInstall;
import com.amazonaws.eclipse.sdk.ui.JavaSdkManager;
import com.amazonaws.eclipse.sdk.ui.JavaSdkPlugin;
import com.amazonaws.eclipse.sdk.ui.classpath.AwsClasspathContainer;
import com.amazonaws.eclipse.sdk.ui.classpath.AwsSdkClasspathUtils;

/**
 * Runnable (with progress) that creates a new AWS Java web project, based on
 * the configured data model. This class is responsible for creating the WTP
 * dynamic web project, adding and configuring the AWS SDK for Java, creating
 * the security credential configuration file and eventually configuring the WTP
 * runtime targeted by the new project.
 */
final class CreateNewAwsJavaWebProjectRunnable implements IRunnableWithProgress {

    private static final String ELASTIC_BEANSTALK_RUNTIME_ID = "com.amazonaws.eclipse.elasticbeanstalk.jee.runtime";
    private static final String GENERIC_JEE_RUNTIME_ID = "org.eclipse.jst.server.core.runtimeType";

    private final NewAwsJavaWebProjectDataModel dataModel;

    /*
     * TODO: it would be better to inspect these from the travel log itself
     * somehow -- right now it's coupled tightly to that file structure.
     */
    public static final String LANGUAGES_DIR = "language";
    public static final Map<String, String> LANGUAGE_DIRS = new HashMap<String, String>();
    static {
        LANGUAGE_DIRS.put(NewAwsJavaWebProjectDataModel.JAPANESE, "jp");
    }

    public static final Map<String, String> LANGUAGE_BUNDLE_PATHS = new HashMap<String, String>();
    static {
        LANGUAGE_BUNDLE_PATHS.put(NewAwsJavaWebProjectDataModel.ENGLISH, "hawaii");
        LANGUAGE_BUNDLE_PATHS.put(NewAwsJavaWebProjectDataModel.JAPANESE, "japan");
    }

    public static final String BUNDLE_BUCKET = "aws-travellog-sample-data";

    private static final IWorkbenchBrowserSupport BROWSER_SUPPORT = PlatformUI.getWorkbench().getBrowserSupport();

    public CreateNewAwsJavaWebProjectRunnable(NewAwsJavaWebProjectDataModel dataModel) {
        this.dataModel = dataModel;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
     */
    public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
        SubMonitor monitor = SubMonitor.convert(progressMonitor, "Creating new AWS Java web project", 100);

        try {
            IRuntime genericJeeServerRuntime = configureGenericJeeServerRuntime();

            // Create a WTP Dynamic Web project
            IDataModel newWebProjectDataModel = DataModelFactory
                    .createDataModel(IWebFacetInstallDataModelProperties.class);
            newWebProjectDataModel.setProperty(IFacetProjectCreationDataModelProperties.FACET_PROJECT_NAME,
                    dataModel.getProjectName());
            if (genericJeeServerRuntime != null) {
                newWebProjectDataModel.setProperty(IFacetProjectCreationDataModelProperties.FACET_RUNTIME,
                        RuntimeManager.getRuntime(genericJeeServerRuntime.getId()));
            }

            // Default to a 2.5 web app
            FacetDataModelMap facetDataModelMap = (FacetDataModelMap) newWebProjectDataModel
                    .getProperty(IFacetProjectCreationDataModelProperties.FACET_DM_MAP);
            IDataModel facetDataModel = facetDataModelMap.getFacetDataModel(IJ2EEFacetConstants.DYNAMIC_WEB);
            facetDataModel.setProperty(IFacetDataModelProperties.FACET_VERSION, IJ2EEFacetConstants.DYNAMIC_WEB_25);

            newWebProjectDataModel.getDefaultOperation().execute(monitor.newChild(30), null);

            // Add the AWS SDK for Java
            IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(dataModel.getProjectName());
            IJavaProject javaProject = JavaCore.create(project);
            JavaSdkManager sdkManager = JavaSdkManager.getInstance();

            // When installing the SDK, make sure we're not in the middle of
            // bootstrapping the environment
            JavaSdkInstall sdkInstall = null;
            Job installationJob = null;
            synchronized (sdkManager) {
                sdkInstall = sdkManager.getDefaultSdkInstall();
                if (sdkInstall == null) {
                    installationJob = sdkManager.getInstallationJob();
                    if (installationJob == null) {
                        JavaSdkPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, JavaSdkPlugin.PLUGIN_ID,
                                "Unable to check status of AWS SDK for Java download"));
                    }
                }
            }

            if (sdkInstall == null && installationJob != null) {
                installationJob.join();
            }

            sdkInstall = sdkManager.getDefaultSdkInstall();
            if (sdkInstall != null) {
                sdkInstall.writeMetadataToProject(javaProject);
                AwsSdkClasspathUtils.addAwsSdkToProjectClasspath(javaProject, sdkInstall);
            }

            monitor.worked(20);

            // Mark it as a Java EE module dependency
            // TODO: If the user changes the SDK version (through the properties page) then we'll lose the
            //       Java EE module dependency classpath entry attribute.
            Map<IClasspathEntry, IPath> classpathEntriesToRuntimePath = new HashMap<IClasspathEntry, IPath>();
            IClasspathEntry entry = findSdkClasspathEntry(javaProject);
            final IPath runtimePath = ClasspathDependencyUtil.getRuntimePath(null, true,
                    ClasspathDependencyUtil.isClassFolderEntry(entry));
            classpathEntriesToRuntimePath.put(entry, runtimePath);
            IDataModelOperation addDependencyAttributesOperation = UpdateClasspathAttributeUtil
                    .createAddDependencyAttributesOperation(project.getName(), classpathEntriesToRuntimePath);
            addDependencyAttributesOperation.execute(monitor.newChild(30), null);

            // Add files to the the project
            addTemplateFiles(project);
            monitor.worked(10);

            // Configure the Tomcat session manager
            if (dataModel.getUseDynamoDBSessionManagement()) {
                addSessionManagerConfigurationFiles(project);
            }
            monitor.worked(10);

            // Open the readme.html in an editor browser window.
            File root = project.getLocation().toFile();
            final File indexHtml = new File(root, "WebContent/index.html");

            // Internal browser must be opened within UI thread
            Display.getDefault().syncExec(new Runnable() {
                public void run() {
                    try {
                        IWebBrowser browser = BROWSER_SUPPORT.createBrowser(IWorkbenchBrowserSupport.AS_EDITOR,
                                null, null, null);
                        browser.openURL(indexHtml.toURI().toURL());
                    } catch (Exception e) {
                        ElasticBeanstalkPlugin.getDefault()
                                .logException("Failed to open project index page in Eclipse editor.", e);
                    }
                }
            });

        } catch (Exception e) {
            throw new InvocationTargetException(e);
        } finally {
            progressMonitor.done();
        }
    }

    private IClasspathEntry findSdkClasspathEntry(IJavaProject javaProject) throws JavaModelException {
        IPath expectedPath = new AwsClasspathContainer(JavaSdkManager.getInstance().getDefaultSdkInstall())
                .getPath();
        for (IClasspathEntry entry : javaProject.getRawClasspath()) {
            if (entry.getPath().equals(expectedPath)) {
                return entry;
            }
        }

        return null;
    }

    private IRuntime configureGenericJeeServerRuntime() {
        // Return the existing AWS generic J2EE runtime if it already exists
        IRuntime runtime = ServerCore.findRuntime(ELASTIC_BEANSTALK_RUNTIME_ID);
        if (runtime != null) {
            return runtime;
        }

        // Otherwise try to create a new one...
        try {
            IRuntimeType jeeRuntimeType = ServerCore.findRuntimeType(GENERIC_JEE_RUNTIME_ID);
            IRuntimeWorkingCopy workingCopy = jeeRuntimeType.createRuntime(ELASTIC_BEANSTALK_RUNTIME_ID,
                    new NullProgressMonitor());
            workingCopy.setName("AWS Elastic Beanstalk J2EE Runtime");

            Bundle bundle = ElasticBeanstalkPlugin.getDefault().getBundle();
            URL url = FileLocator.resolve(bundle.getEntry("/"));

            try {
                File source = new File(url.getFile(), "runtime-lib/j2ee.jar");
                File dest = new File(ElasticBeanstalkPlugin.getDefault().getStateLocation().toFile(),
                        "runtime-lib/j2ee.jar");
                if (!dest.exists()) {
                    FileUtils.copyFile(source, dest);
                }
                workingCopy.setLocation(new Path(dest.getParentFile().getAbsolutePath()));
            } catch (Exception e) {
                // If we can't copy the j2ee jar into the workspace, fall back
                // to using the file in the plugin.
                workingCopy.setLocation(new Path(url.getFile(), "runtime-lib"));
            }

            return workingCopy.save(true, new NullProgressMonitor());
        } catch (Exception e) {
            ElasticBeanstalkPlugin.getDefault().getLog()
                    .log(new Status(Status.ERROR, ElasticBeanstalkPlugin.PLUGIN_ID, e.getMessage(), e));
            return null;
        }
    }

    /** Filename filter that filters out all SVN metadata files. */
    private static final class SvnMetadataFilter implements FileFilter {
        public boolean accept(File pathname) {
            return (pathname.toString().contains("/.svn/") == false);
        }
    }

    private static class CredentialsUtils {
        private static final String AWS_CREDENTIALS_URL = "http://aws.amazon.com/security-credentials";
        private static final String AWS_CREDENTIALS_PROPERTIES_FILE = "AwsCredentials.properties";

        public void addAwsCredentialsFileToProject(final IProject project, String accessKeyId, String secretKey)
                throws CoreException {
            Properties credentialProperties = new Properties();
            credentialProperties.setProperty("accessKey", accessKeyId);
            credentialProperties.setProperty("secretKey", secretKey);

            IPath srcDirPath = project.getLocation().append("src");
            final IPath credentialsFilePath = srcDirPath.append(AWS_CREDENTIALS_PROPERTIES_FILE);

            IFileStore credentialPropertiesFile = EFS.getLocalFileSystem()
                    .fromLocalFile(credentialsFilePath.toFile());
            OutputStream os = credentialPropertiesFile.openOutputStream(EFS.NONE, null);

            try {
                credentialProperties.store(os, "Insert your AWS Credentials from " + AWS_CREDENTIALS_URL);
            } catch (IOException e) {
                Status status = new Status(Status.ERROR, ElasticBeanstalkPlugin.PLUGIN_ID,
                        "Unable to write AWS credentials to file", e);
                throw new CoreException(status);
            } finally {
                try {
                    os.close();
                } catch (Exception e) {
                }
            }

            project.refreshLocal(IResource.DEPTH_INFINITE, null);
        }
    }

    private void addSessionManagerConfigurationFiles(IProject project) throws IOException, CoreException {
        Bundle bundle = ElasticBeanstalkPlugin.getDefault().getBundle();
        URL url = FileLocator.resolve(bundle.getEntry("/"));
        IPath templateRoot = new Path(url.getFile(), "templates");

        FileUtils.copyDirectory(templateRoot.append("dynamodb-session-manager").toFile(),
                project.getLocation().toFile(), new SvnMetadataFilter());

        // Add the user's credentials to context.xml
        File localContextXml = project.getLocation().append(".ebextensions").append("context.xml").toFile();
        AccountInfo accountInfo = AwsToolkitCore.getDefault().getAccountManager()
                .getAccountInfo(dataModel.getAccountId());
        String contextContents = FileUtils.readFileToString(localContextXml);
        contextContents = contextContents.replace("{ACCESS_KEY}", accountInfo.getAccessKey());
        contextContents = contextContents.replace("{SECRET_KEY}", accountInfo.getSecretKey());
        FileUtils.writeStringToFile(localContextXml, contextContents);

        project.refreshLocal(IResource.DEPTH_INFINITE, null);

        // Update the J2EE Deployment Assembly by creating a link from the '/.ebextensions'
        // folder to the '/WEB-INF/.ebextensions' folder in the web assembly mapping for WTP
        IVirtualComponent rootComponent = ComponentCore.createComponent(project);
        IVirtualFolder rootFolder = rootComponent.getRootFolder();
        try {
            Path source = new Path("/.ebextensions");
            Path target = new Path("/WEB-INF/.ebextensions");
            IVirtualFolder subFolder = rootFolder.getFolder(target);
            subFolder.createLink(source, 0, null);
        } catch (CoreException ce) {
            String message = "Unable to configure deployment assembly to map .ebextension directory";
            AwsToolkitCore.getDefault().logException(message, ce);
        }
    }

    private void addTemplateFiles(IProject project) throws IOException, CoreException {
        final String CREDENTIAL_PROFILE_PLACEHOLDER = "{CREDENTIAL_PROFILE}";
        Bundle bundle = ElasticBeanstalkPlugin.getDefault().getBundle();
        URL url = FileLocator.resolve(bundle.getEntry("/"));
        IPath templateRoot = new Path(url.getFile(), "templates");

        AccountInfo currentAccountInfo = AwsToolkitCore.getDefault().getAccountManager()
                .getAccountInfo(dataModel.getAccountId());

        switch (dataModel.getProjectTemplate()) {
        case WORKER:
            File workerServlet = templateRoot.append("worker/src/WorkerServlet.java").toFile();
            String workerServletContent = replaceStringInFile(workerServlet, CREDENTIAL_PROFILE_PLACEHOLDER,
                    currentAccountInfo.getAccountName());

            FileUtils.copyDirectory(templateRoot.append("worker").toFile(), project.getLocation().toFile(),
                    new SvnMetadataFilter());
            FileUtils.writeStringToFile(workerServlet, workerServletContent);
            break;

        case DEFAULT:
            File indexJsp = templateRoot.append("basic/WebContent/index.jsp").toFile();
            String indexJspContent = replaceStringInFile(indexJsp, CREDENTIAL_PROFILE_PLACEHOLDER,
                    currentAccountInfo.getAccountName());

            FileUtils.copyDirectory(templateRoot.append("basic").toFile(), project.getLocation().toFile(),
                    new SvnMetadataFilter());

            FileUtils.writeStringToFile(indexJsp, indexJspContent);
            break;

        default:
            throw new IllegalStateException("Unknown project template: " + dataModel.getProjectTemplate());
        }

        project.refreshLocal(IResource.DEPTH_INFINITE, null);
    }

    /** Replace source strings with target string and return the original content of the file. */
    private String replaceStringInFile(File file, String source, String target) throws IOException {

        String originalContent = FileUtils.readFileToString(file);
        String replacedContent = originalContent.replace(source, target);
        FileUtils.writeStringToFile(file, replacedContent);

        return originalContent;
    }
}