/*******************************************************************************
* Copyright (c) 2013 Atlanmod INRIA LINA Mines Nantes
* 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:
* Atlanmod INRIA LINA Mines Nantes - initial API and implementation
*******************************************************************************/
package fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.internal;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.InvalidRegistryObjectException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import fr.inria.atlanmod.neo4emf.neo4jresolver.Neo4jResolverPlugin;
import fr.inria.atlanmod.neo4emf.neo4jresolver.collections.ObservableArrayList;
import fr.inria.atlanmod.neo4emf.neo4jresolver.logger.Logger;
import fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.AbstractNeo4jRuntimeInstaller;
import fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.INeo4jRuntime;
import fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.INeo4jRuntimesManager;
import fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.JarFilter;
import fr.inria.atlanmod.neo4emf.neo4jresolver.wizards.ImportNeo4jRuntimeWizard;
/**
* @author abelgomez
*
*/
public class Neo4JRuntimesManager implements INeo4jRuntimesManager {
/**
*
*/
private static final String EXPORT_PACKAGE = "Export-Package";
/**
*
*/
private static final String BUNDLE_CLASS_PATH = "Bundle-ClassPath";
/**
*
*/
private static final String BUNDLE_VERSION = "Bundle-Version";
/**
*
*/
private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName";
/**
*
*/
private static final String BUNDLE_NAME = "Bundle-Name";
/**
*
*/
private static final String BUNDLE_MANIFEST_VERSION = "Bundle-ManifestVersion";
private static final String MANIFEST_MF = "MANIFEST.MF";
private static final String META_INF = "META-INF";
private static final String CLASSPATH = ".classpath";
private static final String PROJECT = ".project";
private static final String BUILD_PROPERTIES = "build.properties";
private static final String RUNTIMES_DIR = "runtimes";
private static final String INSTALLERS_EXTENSION_POINT_ID = "fr.inria.atlanmod.neo4emf.neo4jresolver.neo4jInstallers";
private static final String ATT_ID = "id";
private static final String ATT_NAME = "name";
private static final String ATT_VERSION = "version";
private static final String ATT_DESCRIPTION = "description";
private static final String ATT_URL = "url";
private static final String ATT_PATH = "path";
private static final String ATT_HANDLER = "handler";
private static final String CHILD_LICENSE = "License";
private static final String CHILD_ZIPENTRY= "ZipEntry";
public static final Neo4JRuntimesManager INSTANCE;
private static List<INeo4jRuntime> installedRuntimes;
private static List<AbstractNeo4jRuntimeInstaller> installers;
private static IPath dataAreaPath;
private volatile Runnable checkRuntimeRunnable = new Runnable() {
@Override
public void run() {
if (Neo4JRuntimesManager.INSTANCE.getRuntimes().isEmpty()) {
boolean launch = MessageDialog.openQuestion(
getDisplay().getActiveShell(),
"Install Neo4J runtime?",
"No available Neo4J runtimes found. Launch the installation wizard?");
if (launch) {
Neo4JRuntimesManager.INSTANCE.launchInstallRuntimesWizard();
}
}
}
};
static {
INSTANCE = new Neo4JRuntimesManager();
}
public List<INeo4jRuntime> getRuntimes() {
if (installedRuntimes == null) {
load();
}
return installedRuntimes;
}
/* (non-Javadoc)
* @see fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.INeo4jRuntimesManager#checkRuntimes(boolean)
*/
@Override
public synchronized void checkRuntimes() {
getDisplay().syncExec(checkRuntimeRunnable);
}
/* (non-Javadoc)
* @see fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.internal.INeo4jRuntimesManager#getRuntime(java.lang.String)
*/
@Override
public INeo4jRuntime getRuntime(String id) {
for (INeo4jRuntime runtime : getRuntimes()) {
if (runtime.getId() != null && runtime.getId().equals(id)) {
return runtime;
}
} return null;
}
/* (non-Javadoc)
* @see fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.INeo4jRuntimesManager#uninstall(java.lang.String)
*/
// @Override
public void uninstall(String id) throws IOException {
INeo4jRuntime runtime = getRuntime(id);
try {
FileUtils.forceDelete(runtime.getPath().toFile());
} catch (FileNotFoundException e) {
}
load();
}
/* (non-Javadoc)
* @see fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.internal.INeo4jRuntimesManager#launchInstallRuntimesWizard()
*/
// @Override
public void launchInstallRuntimesWizard() {
final Display display = getDisplay();
display.syncExec(new Runnable() {
@Override
public void run() {
Shell shell = display.getActiveShell();
ImportNeo4jRuntimeWizard wizard = new ImportNeo4jRuntimeWizard();
WizardDialog dialog = new WizardDialog(shell, wizard);
dialog.setTitle(wizard.getWindowTitle());
dialog.setBlockOnOpen(true);
dialog.open();
}
});
}
public List<AbstractNeo4jRuntimeInstaller> getInstallers() {
if (installers == null) {
installers = new ArrayList<AbstractNeo4jRuntimeInstaller>();
IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor(INSTALLERS_EXTENSION_POINT_ID);
for (IConfigurationElement element : elements) {
try {
AbstractNeo4jRuntimeInstaller installer = null;
if (element.getAttribute(ATT_HANDLER) != null) {
installer = (AbstractNeo4jRuntimeInstaller) element.createExecutableExtension(ATT_HANDLER);
} else {
Neo4jZippedInstaller zippedInstaller = new Neo4jZippedInstaller();
zippedInstaller.setId(element.getAttribute(ATT_ID));
zippedInstaller.setName(element.getAttribute(ATT_NAME));
zippedInstaller.setVersion(element.getAttribute(ATT_VERSION));
zippedInstaller.setDescription(element.getAttribute(ATT_DESCRIPTION));
zippedInstaller.setId(element.getAttribute(ATT_ID));
zippedInstaller.setUrl(new URL(element.getAttribute(ATT_URL)));
for (IConfigurationElement childElement : element.getChildren(CHILD_LICENSE)) {
zippedInstaller.setLicenseText(childElement.getValue());
}
for (IConfigurationElement childElement : element.getChildren(CHILD_ZIPENTRY)) {
zippedInstaller.getJarFiles().add(childElement.getAttribute(ATT_PATH));
}
installer = zippedInstaller;
}
installers.add(installer);
} catch (CoreException | MalformedURLException | InvalidRegistryObjectException e) {
Logger.log(Logger.SEVERITY_ERROR, e);
}
}
}
return Collections.unmodifiableList(installers);
}
/* (non-Javadoc)
* @see fr.inria.atlanmod.neo4emf.neo4jresolver.runtimes.INeo4jRuntimesManager#getInstaller(java.lang.String)
*/
// @Override
public AbstractNeo4jRuntimeInstaller getInstaller(String id) {
for (AbstractNeo4jRuntimeInstaller installer : getInstallers()) {
if (StringUtils.equals(installer.getId(), id)) {
return installer;
}
}
return null;
}
private void load() {
if (installedRuntimes == null) {
installedRuntimes = new ObservableArrayList<INeo4jRuntime>();
} else {
installedRuntimes.clear();
}
File runtimeDataDir = getRuntimeDataArea().toFile();
if (runtimeDataDir.isDirectory()) {
for (File versionDir : runtimeDataDir.listFiles()) {
if (versionDir.isDirectory()) {
for (File idDir : versionDir.listFiles()) {
INeo4jRuntime runtimeFromLocation = getRuntimeFromLocation(new Path(idDir.getAbsolutePath()));
if (runtimeFromLocation != null) {
installedRuntimes.add(runtimeFromLocation);
}
}
}
}
}
}
public IPath getRuntimeDataArea() {
if (dataAreaPath == null) {
dataAreaPath = Neo4jResolverPlugin.getDefault().getStateLocation().append(RUNTIMES_DIR);
}
return dataAreaPath;
}
public INeo4jRuntime getRuntimeFromLocation(IPath path) {
INeo4jRuntime runtime = null;
File manifestMF = path.append(META_INF).append(MANIFEST_MF).toFile();
if (manifestMF.exists()) {
FileInputStream stream = null;
try {
stream = new FileInputStream(manifestMF);
Manifest manifest = new Manifest(stream);
String id = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLIC_NAME);
String version = manifest.getMainAttributes().getValue(BUNDLE_VERSION);
runtime = new Neo4jRuntime(version, id, path);
} catch (Exception e) {
Logger.log(Logger.SEVERITY_ERROR, e);
} finally {
IOUtils.closeQuietly(stream);
}
}
return runtime;
}
public void initializeRuntimeMetadata(AbstractNeo4jRuntimeInstaller installer, IPath path) {
File dirFile = path.toFile();
File manifestFile = path.append(META_INF).append(MANIFEST_MF).toFile();
FileOutputStream outputStream = null;
try {
FileUtils.forceMkdir(manifestFile.getParentFile());
outputStream = new FileOutputStream(manifestFile);
Manifest manifest = new Manifest();
Attributes atts = manifest.getMainAttributes();
atts.put(Attributes.Name.MANIFEST_VERSION, "1.0");
atts.putValue(BUNDLE_MANIFEST_VERSION, "2");
atts.putValue(BUNDLE_NAME, installer.getName());
atts.putValue(BUNDLE_SYMBOLIC_NAME, installer.getId());
atts.putValue(BUNDLE_VERSION, installer.getVersion());
atts.putValue(BUNDLE_CLASS_PATH, buildJarFilesList(dirFile));
atts.putValue(EXPORT_PACKAGE, buildPackagesList(dirFile));
manifest.write(outputStream);
load();
} catch (Exception e) {
Logger.log(Logger.SEVERITY_ERROR, e);
} finally {
IOUtils.closeQuietly(outputStream);
}
}
public void configureAsProjectPlugin(AbstractNeo4jRuntimeInstaller installer, IPath path) {
File dirFile = path.toFile();
File projectFile = path.append(PROJECT).toFile();
Writer writer = null;
try {
FileUtils.forceMkdir(projectFile.getParentFile());
writer = new FileWriter(projectFile);
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
writer.write("<projectDescription>\n");
writer.write(" <name>" + installer.getId() + "</name>\n");
writer.write(" <comment></comment>\n");
writer.write(" <projects>\n");
writer.write(" </projects>\n");
writer.write(" <buildSpec>\n");
writer.write(" <buildCommand>\n");
writer.write(" <name>org.eclipse.jdt.core.javabuilder</name>\n");
writer.write(" <arguments>\n");
writer.write(" </arguments>\n");
writer.write(" </buildCommand>\n");
writer.write(" <buildCommand>\n");
writer.write(" <name>org.eclipse.pde.ManifestBuilder</name>\n");
writer.write(" <arguments>\n");
writer.write(" </arguments>\n");
writer.write(" </buildCommand>\n");
writer.write(" <buildCommand>\n");
writer.write(" <name>org.eclipse.pde.SchemaBuilder</name>\n");
writer.write(" <arguments>\n");
writer.write(" </arguments>\n");
writer.write(" </buildCommand>\n");
writer.write(" </buildSpec>\n");
writer.write(" <natures>\n");
writer.write(" <nature>org.eclipse.pde.PluginNature</nature>\n");
writer.write(" <nature>org.eclipse.jdt.core.javanature</nature>\n");
writer.write(" </natures>\n");
writer.write("</projectDescription>\n");
} catch (Exception e) {
Logger.log(Logger.SEVERITY_ERROR, e);
} finally {
IOUtils.closeQuietly(writer);
}
File classPathFile = path.append(CLASSPATH).toFile();
try {
FileUtils.forceMkdir(classPathFile.getParentFile());
writer = new FileWriter(classPathFile);
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
writer.write("<classpath>\n");
writer.write(" <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/>\n");
writer.write(" <classpathentry kind=\"con\" path=\"org.eclipse.pde.core.requiredPlugins\"/>\n");
for (String jarFile : getJarFilesList(dirFile)) {
writer.write(" <classpathentry exported=\"true\" kind=\"lib\" path=\"" + jarFile + "\"/>\n");
}
writer.write("</classpath>\n");
} catch (Exception e) {
Logger.log(Logger.SEVERITY_ERROR, e);
} finally {
IOUtils.closeQuietly(writer);
}
File buildPropertiesFile = path.append(BUILD_PROPERTIES).toFile();
try {
FileUtils.forceMkdir(buildPropertiesFile.getParentFile());
writer = new FileWriter(buildPropertiesFile);
writer.write("bin.includes = META-INF/,\\\n");
writer.write(" .\n");
} catch (Exception e) {
Logger.log(Logger.SEVERITY_ERROR, e);
} finally {
IOUtils.closeQuietly(writer);
}
}
private List<String> getJarFilesList(File directory) {
List<String> files = new ArrayList<String>();
for (File file : directory.listFiles(new JarFilter())) {
files.add(file.getName());
}
return files;
}
private String buildJarFilesList(File directory) {
final String SEPARATOR = ", ";
StringBuilder builder = new StringBuilder();
if (directory != null && directory.isDirectory()) {
for (String fileName : getJarFilesList(directory)) {
builder.append(fileName);
builder.append(SEPARATOR);
}
if (builder.length() >= SEPARATOR.length()) {
builder.setLength(builder.length() - SEPARATOR.length());
}
return builder.toString();
} else {
return null;
}
}
private String buildPackagesList(File directory) {
Set<String> packages = new HashSet<>();
final String SEPARATOR = ", ";
StringBuilder builder = new StringBuilder();
if (directory != null && directory.isDirectory()) {
for (File file : directory.listFiles(new JarFilter())) {
ZipInputStream zipStream = null;
try {
zipStream = new ZipInputStream(new FileInputStream(file));
ZipEntry entry;
while((entry = zipStream.getNextEntry()) != null) {
if (FilenameUtils.getExtension(entry.getName()).equals("class")) {
packages.add(FilenameUtils.getPathNoEndSeparator(
entry.getName()).replace('/', '.'));
}
}
} catch (IOException e) {
Logger.log(Logger.SEVERITY_ERROR, e);
} finally {
IOUtils.closeQuietly(zipStream);
}
}
for (String pkg : packages) {
builder.append(pkg);
builder.append(SEPARATOR);
}
if (builder.length() >= SEPARATOR.length()) {
builder.setLength(builder.length() - SEPARATOR.length());
}
return builder.toString();
} else {
return null;
}
}
private Display getDisplay() {
return Display.getCurrent() != null ? Display.getCurrent() : Display.getDefault();
}
}