/*
* 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;
}
}