/*******************************************************************************
* Copyright (c) 2012, 2015 Pivotal Software, Inc.
* 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:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.internal.configurator;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.service.resolver.VersionRange;
import org.eclipse.wst.server.ui.internal.ServerUIPlugin;
import org.osgi.framework.Version;
import org.springframework.util.StringUtils;
import org.springsource.ide.eclipse.commons.configurator.ConfigurableExtension;
import org.springsource.ide.eclipse.commons.configurator.IConfigurationContext;
import org.springsource.ide.eclipse.commons.configurator.WorkspaceConfiguratorParticipant;
import org.springsource.ide.eclipse.commons.core.FileUtil;
import org.springsource.ide.eclipse.commons.core.StatusHandler;
import org.springsource.ide.eclipse.commons.ui.IIdeUiStartup;
/**
* Automatically adds server runtimes and sample projects to the workspace by
* scanning the local disk.
* @author Steffen Pingel
* @author Christian Dupuis
* @author Terry Denney
* @author Leo Dos Santos
* @author Martin Lippert
*/
@SuppressWarnings("restriction")
public class ConfiguratorImporter implements IIdeUiStartup, IConfigurationContext, IConfigurator {
public static class ParticipantExtensionPointReader {
private static final String ELEMENT_INSTALLABLEITEM = "installableItem";
private static final String ELEMENT_PARTICIPANT = "participant";
private static final String EXTENSION_ID_PARTICIPANT = "com.springsource.sts.ide.configurator.participant";
public static Set<InstallableItem> getInstallableItems() {
Set<InstallableItem> items = new HashSet<InstallableItem>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint extensionPoint = registry.getExtensionPoint(EXTENSION_ID_PARTICIPANT);
IExtension[] extensions = extensionPoint.getExtensions();
for (IExtension extension : extensions) {
IConfigurationElement[] elements = extension.getConfigurationElements();
for (IConfigurationElement element : elements) {
if (element.getName().equals(ELEMENT_INSTALLABLEITEM)) {
InstallableItem ext = new InstallableItem(element);
items.add(ext);
}
}
}
return items;
}
public static Set<WorkspaceConfiguratorParticipant> getParticipants() {
Set<WorkspaceConfiguratorParticipant> configurators = new HashSet<WorkspaceConfiguratorParticipant>();
Set<ParticipantDescriptor> extensions = getPartipantExtensions();
for (ParticipantDescriptor extension : extensions) {
WorkspaceConfiguratorParticipant configurator = extension.createConfigurator();
if (configurator != null) {
configurators.add(configurator);
}
}
return configurators;
}
public static Set<ParticipantDescriptor> getPartipantExtensions() {
Set<ParticipantDescriptor> configurators = new HashSet<ParticipantDescriptor>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint extensionPoint = registry.getExtensionPoint(EXTENSION_ID_PARTICIPANT);
IExtension[] extensions = extensionPoint.getExtensions();
for (IExtension extension : extensions) {
IConfigurationElement[] elements = extension.getConfigurationElements();
for (IConfigurationElement element : elements) {
if (element.getName().equals(ELEMENT_PARTICIPANT)) {
ParticipantDescriptor ext = new ParticipantDescriptor(element);
configurators.add(ext);
}
}
}
return configurators;
}
}
private static final CountDownLatch lazyStartupJobLatch = new CountDownLatch(1);
private static final String SAMPLES_PATH = "samples";
public static CountDownLatch getLazyStartupJobLatch() {
return lazyStartupJobLatch;
}
/**
* Returns true if <code>name</code> matches the expected constraints
* expressed by the other parameters.
*
* @param name the name of the directory on disk
* @param path the expected path prefix
* @param versionRange the expected version range
* @return
*/
public static boolean matches(String name, String path, VersionRange versionRange) {
if (path == null || !name.startsWith(path)) {
return false;
}
Version version = getVersion(name);
if (version != null && versionRange != null) {
return versionRange.isIncluded(version);
}
return true;
}
private static File getFileFromLocation(Location userLocation) {
if (userLocation != null) {
return new File(userLocation.getURL().getFile());
}
return null;
}
private static Version getVersion(String name) {
int i = name.lastIndexOf("-");
if (i != -1) {
try {
return new Version(name.substring(i + 1));
}
catch (IllegalArgumentException e) {
// ignore
}
}
return Version.emptyVersion;
}
private boolean firstMatchOnly;
private boolean recurse;
private List<File> searchLocations;
private boolean scanInstallPath;
public ConfiguratorImporter() {
setRecurse(false);
setFirstMatchOnly(true);
setScanInstallPath(true);
}
public boolean isScanInstallPath() {
return scanInstallPath;
}
public void setScanInstallPath(boolean scanInstallPath) {
this.scanInstallPath = scanInstallPath;
}
public List<ConfigurableExtension> detectExtensions(final IProgressMonitor monitor) {
final List<ConfigurableExtension> result = new ArrayList<ConfigurableExtension>();
Set<WorkspaceConfiguratorParticipant> participants = ParticipantExtensionPointReader.getParticipants();
for (final WorkspaceConfiguratorParticipant participant : participants) {
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable exception) {
// logged by super class
}
public void run() throws Exception {
List<ConfigurableExtension> extensions = participant.detectExtensions(ConfiguratorImporter.this,
monitor);
result.addAll(extensions);
}
});
}
Set<InstallableItem> installableItems = ParticipantExtensionPointReader.getInstallableItems();
for (final InstallableItem item : installableItems) {
boolean found = false;
for (ConfigurableExtension extension : result) {
if (extension.getId().equals(item.getId())) {
extension.setInstallableItem(item);
found = true;
}
}
if (!found) {
for (final WorkspaceConfiguratorParticipant participant : participants) {
if (participant.getId().equals(item.getConfiguratorId())) {
final AtomicBoolean added = new AtomicBoolean(false);
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable exception) {
// logged by super class
}
public void run() throws Exception {
ConfigurableExtension extension = participant.createExtension(item, monitor);
if (extension != null) {
result.add(extension);
added.set(true);
}
}
});
if (added.get()) {
break;
}
}
}
}
}
return result;
}
public File getDefaultInstallLocation() {
List<File> locations = getSearchLocations();
for (File location : locations) {
if (location.exists() && location.canWrite()) {
return location;
}
}
return null;
}
public Set<String> getInstalledBundles() {
final Set<String> installedBundles = new HashSet<String>();
Set<WorkspaceConfiguratorParticipant> participants = ParticipantExtensionPointReader.getParticipants();
for (final WorkspaceConfiguratorParticipant configurator : participants) {
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable exception) {
// logged by super class
}
public void run() throws Exception {
List<ConfigurableExtension> extensions = configurator.detectExtensions(ConfiguratorImporter.this,
new NullProgressMonitor());
for (ConfigurableExtension extension : extensions) {
if (extension.getBundleId() != null) {
installedBundles.add(extension.getBundleId() + ".feature.group");
}
}
}
});
}
return installedBundles;
}
public File getInstallLocation() {
String path = Activator.getDefault().getPreferenceStore().getString(Activator.PROPERTY_USER_INSTALL_PATH);
return (path != null) ? new File(path) : null;
}
/**
* Returns true, if directories are scanned recursively going upwards to the
* root of the file-system.
*/
public boolean getRecurse() {
return recurse;
}
/**
* Returns a list of directories to scan.
*/
public List<File> getSearchLocations() {
if (searchLocations == null) {
List<File> locations = new ArrayList<File>();
File file = getSystemLocation();
if (file != null) {
locations.add(file);
}
file = getFileFromLocation(Platform.getUserLocation());
if (file != null) {
locations.add(file);
}
String home = System.getProperty("user.home");
if (home != null) {
locations.add(new File(home));
}
searchLocations = locations;
}
return searchLocations;
}
public File getSystemLocation() {
File file = getFileFromLocation(Platform.getInstallLocation());
if (file != null && file.getParentFile() != null) {
File systemLocation = file.getParentFile();
// check new OSX app layout and select the parent folder outside of
// the .app directory instead of a folder inside of the .app folder
if (Platform.OS_MACOSX.equals(Platform.getOS())) {
Pattern pattern = Pattern.compile("(.+)/.*\\.app/Contents");
Matcher m = pattern.matcher(systemLocation.getAbsolutePath());
if (m.find()) {
File auxFile = new File(m.group(1));
if (auxFile.exists()) {
systemLocation = auxFile;
}
}
}
return systemLocation;
}
return null;
}
public boolean isFirstMatchOnly() {
return firstMatchOnly;
}
/**
* @deprecated
*/
@Deprecated
public boolean isInstalled(String bundleId) {
return getInstalledBundles().contains(bundleId);
}
public void lazyStartup() {
List<String> commandLineArgs = Arrays.asList(Platform.getCommandLineArgs());
if (commandLineArgs.contains("-no-autoconfiguration")) {
return;
}
/*
* p2 has a bug (ah, more then one actually): it can't make its mind if
* it wants sts.ini or STS.ini so on case sensitive file systems we copy
* it file again.
*/
if (Platform.getOS().equals(Platform.OS_MACOSX)) {
File upperCaseFile = new File(".", "STS.ini");
File lowerCaseFile = new File(".", "sts.ini");
// Check if STS.ini exists. This will fail if we don't run in the
// STS distribution; also check if sts.ini already exists. This
// covers the case where STS.ini exists and we run on a
// case-insensitive file system where sts.ini would also exist
// already. Otherwise copy file over.
if (upperCaseFile.exists() && !lowerCaseFile.exists()) {
try {
FileUtil.copyFile(upperCaseFile, lowerCaseFile, new NullProgressMonitor());
}
catch (CoreException e) {
StatusHandler.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
"Could not copy STS.ini to sts.ini", e));
}
}
}
// Check if we should run at all
final boolean configured = Activator.getDefault().getPreferenceStore()
.getBoolean(Activator.PROPERTY_CONFIGURATOR_PROCESSED);
final boolean pendingRequests = StringUtils.hasLength(Activator.getDefault().getPreferenceStore()
.getString(Activator.PROPERTY_CONFIGURE_TARGETS));
if (configured && !pendingRequests) {
return;
}
Job importJob = new Job("Workspace Configuration") {
@Override
public IStatus run(IProgressMonitor monitor) {
// Rerun workspace participants
if (pendingRequests) {
Configurator action = new Configurator();
action.executePendingRequests();
}
// Check if import already ran on the workspace
if (configured) {
return Status.OK_STATUS;
}
// Mark workspace as being processed before running to avoid
// re-running on failure
Activator.getDefault().getPreferenceStore().setValue(Activator.PROPERTY_CONFIGURATOR_PROCESSED, true);
// Save servers view state for later reset
boolean isShowOnActivity = ServerUIPlugin.getPreferences().getShowOnActivity();
// Prevent the servers view from showing up
ServerUIPlugin.getPreferences().setShowOnActivity(false);
// Import the sample projects
List<File> samplesPath = scan(SAMPLES_PATH, null);
if (samplesPath.size() > 0) {
for (File sample : samplesPath.get(0).listFiles()) {
createProject(monitor, sample);
}
}
// Run external contributed configurators
List<ConfigurableExtension> extensions = detectExtensions(monitor);
for (ConfigurableExtension extension : extensions) {
// Only configure extensions marked as auto configurable to
// avoid adding old runtime/server versions
if (extension.isAutoConfigurable()) {
extension.configure(monitor);
}
}
// Reset the servers view to original state
ServerUIPlugin.getPreferences().setShowOnActivity(isShowOnActivity);
lazyStartupJobLatch.countDown();
return Status.OK_STATUS;
}
};
importJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
importJob.schedule();
}
public List<File> scan(String path, VersionRange versionRange) {
List<File> matches = new ArrayList<File>();
List<File> locations = getSearchLocations();
if (isScanInstallPath()) {
if (!locations.contains(getInstallLocation())) {
locations.add(0, getInstallLocation());
}
}
outerLoop: for (File location : locations) {
// Iterate up till dm Server, tc Server or samples dir is found
while (location != null) {
File[] files = location.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory() && matches(file.getName(), path, versionRange)) {
matches.add(file);
}
}
}
if (!matches.isEmpty() && firstMatchOnly) {
break outerLoop;
}
if (!recurse) {
break;
}
location = location.getParentFile();
}
}
if (!matches.isEmpty()) {
Collections.sort(matches, new Comparator<File>() {
/**
* Sorts high versions first and path names with invalid
* versions last.
*/
public int compare(File o1, File o2) {
Version v1 = getVersion(o1.getName());
Version v2 = getVersion(o2.getName());
if (v1 == null) {
return (v2 != null) ? 1 : 0;
}
else if (v2 == null) {
return -1;
}
return -v1.compareTo(v2);
}
});
return matches;
}
return Collections.emptyList();
}
public void setFirstMatchOnly(boolean firstMatchOnly) {
this.firstMatchOnly = firstMatchOnly;
}
public void setInstallLocation(File location) {
Activator.getDefault().getPreferenceStore()
.setValue(Activator.PROPERTY_USER_INSTALL_PATH, location.getAbsolutePath());
}
/**
* @see #getRecurse()
*/
public void setRecurse(boolean recurse) {
this.recurse = recurse;
}
public void setSearchLocations(List<File> searchLocations) {
this.searchLocations = new ArrayList<File>(searchLocations);
}
private void createProject(IProgressMonitor monitor, File sample) {
if (sample.isDirectory()) {
try {
IProjectDescription desc = ResourcesPlugin.getWorkspace().loadProjectDescription(
new Path(sample.getAbsolutePath()).append(".project"));
if (desc != null) {
String projectName = desc.getName();
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject project = workspace.getRoot().getProject(projectName);
project.create(desc, new SubProgressMonitor(monitor, 30));
project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 70));
}
}
catch (CoreException e) {
StatusHandler.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "An error occurred creating project",
e));
}
}
}
}