/*******************************************************************************
* Copyright (c) 2014, 2015 IBM Corporation and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.cf.nodejs;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.Path;
import org.eclipse.orion.server.cf.ds.IDeploymentPlanner;
import org.eclipse.orion.server.cf.ds.objects.Plan;
import org.eclipse.orion.server.cf.ds.objects.Procfile;
import org.eclipse.orion.server.cf.manifest.v2.InvalidAccessException;
import org.eclipse.orion.server.cf.manifest.v2.ManifestParseTree;
import org.eclipse.orion.server.cf.manifest.v2.utils.ManifestConstants;
import org.eclipse.orion.server.cf.manifest.v2.utils.ManifestUtils;
import org.eclipse.orion.server.core.IOUtilities;
import org.eclipse.orion.server.core.OrionConfiguration;
import org.eclipse.osgi.util.NLS;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NodeJSDeploymentPlanner implements IDeploymentPlanner {
protected final Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.cf"); //$NON-NLS-1$
public static String TYPE = "node.js"; //$NON-NLS-1$
@Override
public String getId() {
return getClass().getCanonicalName();
}
@Override
public String getWizardId() {
return "org.eclipse.orion.client.cf.wizard.nodejs"; //$NON-NLS-1$
}
protected String getApplicationName(IFileStore contentLocation) throws UnsupportedEncodingException {
IFileStore rootStore = OrionConfiguration.getRootLocation();
Path relativePath = new Path(URLDecoder.decode(contentLocation.toURI().toString(), "UTF8").substring(rootStore.toURI().toString().length()));
if (relativePath.segmentCount() < 4) {
// not a change to a file in a project
return null;
}
String projectDirectory = relativePath.segment(3);
projectDirectory = projectDirectory.replaceFirst(" \\| ", " --- ");
String[] folderNameParts = projectDirectory.split(" --- ", 2);
if (folderNameParts.length > 1)
return folderNameParts[1];
return folderNameParts[0];
}
protected String getApplicationHost(IFileStore contentLocation) throws UnsupportedEncodingException {
IFileStore rootStore = OrionConfiguration.getRootLocation();
Path relativePath = new Path(URLDecoder.decode(contentLocation.toURI().toString(), "UTF8").substring(rootStore.toURI().toString().length()));
if (relativePath.segmentCount() < 4) {
// not a change to a file in a project
return null;
}
String folderName = relativePath.segment(3);
folderName = folderName.replaceFirst(" \\| ", " --- ");
return ManifestUtils.slugify(folderName);
}
protected void set(ManifestParseTree application, String property, String defaultValue) {
if (application.has(property))
return;
else
application.put(property, defaultValue);
}
/**
* Looks for a Procfile and parses the web command.
* @return <code>null</code> iff there is no Procfile present or it does not contain a web command.
*/
protected String getProcfileCommand(IFileStore contentLocation) {
IFileStore procfileStore = contentLocation.getChild(ManifestConstants.PROCFILE);
if (!procfileStore.fetchInfo().exists())
return null;
InputStream is = null;
try {
is = procfileStore.openInputStream(EFS.NONE, null);
Procfile procfile = Procfile.load(is);
return procfile.get(ManifestConstants.WEB);
} catch (Exception ex) {
/* can't parse the file, fail */
return null;
} finally {
IOUtilities.safeClose(is);
}
}
/**
* Looks for the package.json and parses the start command.
* @return <code>null</code> iff the package.json does not contain an explicit start command.
*/
protected String getPackageCommand(IFileStore contentLocation) {
IFileStore packageStore = contentLocation.getChild(NodeJSConstants.PACKAGE_JSON);
if (!packageStore.fetchInfo().exists())
return null;
InputStream is = null;
try {
is = packageStore.openInputStream(EFS.NONE, null);
JSONObject packageJSON = new JSONObject(new JSONTokener(new InputStreamReader(is)));
if (packageJSON.has(NodeJSConstants.SCRIPTS)) {
JSONObject scripts = packageJSON.getJSONObject(NodeJSConstants.SCRIPTS);
if (scripts.has(NodeJSConstants.START))
return scripts.getString(NodeJSConstants.START);
}
} catch (Exception ex) {
/* can't parse the file, fail */
return null;
} finally {
IOUtilities.safeClose(is);
}
return null;
}
/**
* Looks for the app.js or server.js files and creates a start command.
* @return <code>null</code> iff both app.js and server.js are absent.
*/
protected String getConventionCommand(IFileStore contentLocation) {
IFileStore serverJS = contentLocation.getChild(NodeJSConstants.SERVER_JS);
if (serverJS.fetchInfo().exists())
return NodeJSConstants.NODE_SERVER_JS;
IFileStore appJS = contentLocation.getChild(NodeJSConstants.APP_JS);
if (appJS.fetchInfo().exists())
return NodeJSConstants.NODE_APP_JS;
return null;
}
@Override
public Plan getDeploymentPlan(IFileStore contentLocation, ManifestParseTree manifest, IFileStore manifestStore) {
IFileStore appStore = contentLocation;
try {
if (manifest != null) {
ManifestParseTree application = manifest.get(ManifestConstants.APPLICATIONS).get(0);
if (application.has(ManifestConstants.PATH)) {
appStore = contentLocation.getFileStore(new Path(application.get(ManifestConstants.PATH).getValue()));
}
}
} catch (InvalidAccessException e) {
logger.error("Problem while reading manifest", e);
}
/* a present package.json file determines a node.js application */
IFileStore packageStore = appStore.getChild(NodeJSConstants.PACKAGE_JSON);
if (!packageStore.fetchInfo().exists())
return null;
/* do not support multi-application manifests */
if (manifest != null && ManifestUtils.hasMultipleApplications(manifest))
return null;
try {
String manifestPath;
if (manifest == null) {
manifest = ManifestUtils.createBoilerplate(getApplicationName(contentLocation));
manifestPath = null;
} else {
manifestPath = contentLocation.toURI().relativize(manifestStore.toURI()).toString();
}
ManifestParseTree application = manifest.get(ManifestConstants.APPLICATIONS).get(0);
String defaultName = getApplicationName(contentLocation);
set(application, ManifestConstants.NAME, defaultName);
set(application, ManifestConstants.HOST, getApplicationHost(contentLocation));
set(application, ManifestConstants.MEMORY, ManifestUtils.DEFAULT_MEMORY);
set(application, ManifestConstants.INSTANCES, ManifestUtils.DEFAULT_INSTANCES);
set(application, ManifestConstants.PATH, ManifestUtils.DEFAULT_PATH);
/* node.js application require a start command */
if (application.has(ManifestConstants.COMMAND))
return new Plan(getId(), getWizardId(), TYPE, manifest, manifestPath);
/* look up Procfile */
String command = getProcfileCommand(appStore);
if (command != null) {
/* Do not set the command, buildpack will handle it */
// application.put(ManifestConstants.COMMAND, command);
return new Plan(getId(), getWizardId(), TYPE, manifest, manifestPath);
}
/* look up package.json */
command = getPackageCommand(appStore);
if (command != null) {
/* Do not set the command, buildpack will handle it */
// application.put(ManifestConstants.COMMAND, command);
return new Plan(getId(), getWizardId(), TYPE, manifest, manifestPath);
}
command = getConventionCommand(appStore);
if (command != null) {
application.put(ManifestConstants.COMMAND, command);
return new Plan(getId(), getWizardId(), TYPE, manifest, manifestPath);
}
/* could not deduce command, mark as required */
Plan plan = new Plan(getId(), getWizardId(), TYPE, manifest, manifestPath);
plan.addRequired(ManifestConstants.COMMAND);
return plan;
} catch (Exception ex) {
String msg = NLS.bind("Failed to handle generic deployment plan for {0}", contentLocation.toString()); //$NON-NLS-1$
logger.error(msg, ex);
return null;
}
}
}