/*
* Carrot2 project.
*
* Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.workbench.core;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.carrot2.core.Controller;
import org.carrot2.core.ControllerFactory;
import org.carrot2.core.DocumentSourceDescriptor;
import org.carrot2.core.IClusteringAlgorithm;
import org.carrot2.core.IDocumentSource;
import org.carrot2.core.ProcessingComponentDescriptor;
import org.carrot2.core.ProcessingComponentSuite;
import org.carrot2.shaded.guava.common.base.Objects;
import org.carrot2.shaded.guava.common.collect.Lists;
import org.carrot2.shaded.guava.common.collect.Maps;
import org.carrot2.text.linguistic.DefaultLexicalDataFactoryDescriptor;
import org.carrot2.util.attribute.BindableDescriptor;
import org.carrot2.util.resource.DirLocator;
import org.carrot2.util.resource.IResource;
import org.carrot2.util.resource.IResourceLocator;
import org.carrot2.util.resource.PrefixDecoratorLocator;
import org.carrot2.util.resource.ResourceLookup;
import org.carrot2.util.resource.ResourceLookup.Location;
import org.carrot2.workbench.core.helpers.Utils;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The activator class (plug-in's entry point), controls the life-cycle and contains a
* reference to the Carrot2 {@link Controller}.
*/
public class WorkbenchCorePlugin extends AbstractUIPlugin
{
/** Plug-in ID. */
public static final String PLUGIN_ID = "org.carrot2.workbench.core";
/** Source extension identifier. */
public static final String COMPONENT_SUITE_EXTENSION_ID = "org.carrot2.core.componentSuite";
/** The shared instance. */
private static WorkbenchCorePlugin plugin;
/**
* Shared, thread-safe caching controller instance.
*/
private Controller controller;
/**
* All loaded components ({@link IDocumentSource}s and {@link IClusteringAlgorithm}.
*/
private ProcessingComponentSuite componentSuite;
/**
* Cached {@link BindableDescriptor}s of all available components in
* {@link #componentSuite}.
*/
private HashMap<String, BindableDescriptor> bindableDescriptors = Maps.newHashMap();
/**
* Cached component bindableDescriptors of all available components in
* {@link #componentSuite}.
*/
private HashMap<String, ProcessingComponentDescriptor> processingDescriptors = Maps
.newHashMap();
/**
* Cached image descriptors of components.
*/
private HashMap<String, ImageDescriptor> componentImages = Maps.newHashMap();
/**
* List of failed components.
*/
private List<ProcessingComponentDescriptor> failed = Lists.newArrayList();
/**
* Workspace locator.
*/
private IResourceLocator workspaceLocator;
/**
* Starts the bundle: scan suites and initialize the controller.
*/
public void start(BundleContext context) throws Exception
{
super.start(context);
plugin = this;
// Fix instance location first.
fixInstanceLocation();
// Workspace resource locator.
workspaceLocator = getWorkspaceResourceLocator();
// Scan the list of suite extension points.
scanSuites();
ArrayList<IResourceLocator> locators = Lists.newArrayList();
if (workspaceLocator != null)
{
locators.add(workspaceLocator);
}
locators.add(Location.CONTEXT_CLASS_LOADER.locator);
Map<String, Object> initAttributes = Maps.newHashMap();
initAttributes.put(
DefaultLexicalDataFactoryDescriptor.Keys.RESOURCE_LOOKUP,
new ResourceLookup(locators));
controller = ControllerFactory.createCachingPooling(IDocumentSource.class);
controller.init(initAttributes, componentSuite.getComponentConfigurations());
}
private void fixInstanceLocation()
{
Logger logger = LoggerFactory.getLogger(WorkbenchCorePlugin.class);
org.eclipse.osgi.service.datalocation.Location instanceLocation =
Platform.getInstanceLocation();
if (!instanceLocation.isSet())
{
org.eclipse.osgi.service.datalocation.Location installLocation =
Platform.getInstallLocation();
if (installLocation.isSet())
{
try
{
// CARROT-1147: if instance location is inside the .app folder, search for
// workspace.
if (Objects.equal(Platform.getOS(), Platform.OS_MACOSX)) {
Path installPath = Paths.get(installLocation.getURL().toURI());
Path workspace = installPath.resolve("../../../workspace");
if (Files.exists(workspace)) {
instanceLocation.set(workspace.toUri().toURL(), true);
} else {
instanceLocation.set(Platform.getUserLocation().getDataArea("workspace"), true);
}
} else {
instanceLocation.set(installLocation.getDataArea("workspace"), true);
}
logger.info("Changed instanceLocation to: " + instanceLocation.getURL());
}
catch (Exception e)
{
logger.error("Unable to set instanceLocation to: " + instanceLocation.getURL());
}
}
else
{
logger.error("Could not determine install location.");
}
}
else
{
logger.info("Instance location already set to: " + instanceLocation.getURL());
}
logger.debug("User location: " + Platform.getUserLocation().getURL());
logger.debug("Install location: " + Platform.getInstallLocation().getURL());
logger.debug("Instance location: " + Platform.getInstanceLocation().getURL());
logger.debug("Platform working location: " + Platform.getLocation());
logger.debug("Configuration location: " + Platform.getConfigurationLocation().getURL());
}
/*
*
*/
public void stop(BundleContext context) throws Exception
{
plugin = null;
controller.dispose();
controller = null;
super.stop(context);
}
/**
* Returns an initialized shared controller instance.
*/
public Controller getController()
{
return controller;
}
/**
* Returns an image descriptor for the image file at the given plug-in relative path.
*
* @param path the path
* @return the image descriptor
*/
public static ImageDescriptor getImageDescriptor(String path)
{
return imageDescriptorFromPlugin(PLUGIN_ID, path);
}
/**
* Returns all loaded components ({@link IClusteringAlgorithm} and
* {@link IDocumentSource}.
*/
public ProcessingComponentSuite getComponentSuite()
{
return componentSuite;
}
/**
* Returns a {@link BindableDescriptor} for a given component ID or <code>null</code>
* if this component is not available.
*/
public BindableDescriptor getComponentDescriptor(String componentID)
{
return bindableDescriptors.get(componentID);
}
/**
* Returns a {@link ProcessingComponentDescriptor} for a given component ID or
* <code>null<code>.
*/
public ProcessingComponentDescriptor getComponent(String componentID)
{
return processingDescriptors.get(componentID);
}
/**
* Returns a {@link ImageDescriptor} for a given component or a default image if the
* component did not contain any icon.
*/
public ImageDescriptor getComponentImageDescriptor(String componentID)
{
ImageDescriptor d = componentImages.get(componentID);
if (d == null)
{
d = getImageDescriptor("icons/missing-component.png");
}
return d;
}
/**
* Returns the shared instance.
*/
public static WorkbenchCorePlugin getDefault()
{
return plugin;
}
/**
* Scan all declared extensions of {@link #COMPONENT_SUITE_EXTENSION_ID} extension
* point.
*/
private void scanSuites()
{
final List<ProcessingComponentSuite> suites = Lists.newArrayList();
final IExtension [] extensions = Platform.getExtensionRegistry()
.getExtensionPoint(COMPONENT_SUITE_EXTENSION_ID).getExtensions();
// Load suites from extension points.
for (IExtension extension : extensions)
{
final IConfigurationElement [] configElements = extension
.getConfigurationElements();
if (configElements.length == 1 && "suite".equals(configElements[0].getName()))
{
String suiteRoot = configElements[0].getAttribute("resourceRoot");
if (StringUtils.isEmpty(suiteRoot)) suiteRoot = "";
final String suiteResourceName = configElements[0].getAttribute("resource");
if (StringUtils.isEmpty(suiteResourceName))
{
continue;
}
String bundleId = configElements[0].getAttribute("bundleId");
if (StringUtils.isEmpty(bundleId))
{
final IContributor c = extension.getContributor();
bundleId = c.getName();
}
final Bundle b = Platform.getBundle(bundleId);
if (b == null)
{
Utils.logError("Suite's bundle not found: " + bundleId, false);
continue;
}
if (b.getState() != Bundle.ACTIVE)
{
try
{
b.start();
}
catch (BundleException e)
{
Utils.logError("Bundle inactive: " + bundleId, false);
continue;
}
}
ArrayList<IResourceLocator> locators = new ArrayList<>();
if (workspaceLocator != null) {
locators.add(workspaceLocator);
}
locators.add(new PrefixDecoratorLocator(new BundleResourceLocator(b), suiteRoot));
final ResourceLookup resourceLookup = new ResourceLookup(locators);
IResource suiteResource = resourceLookup.getFirst(suiteResourceName);
if (suiteResource == null)
{
String message = "Suite extension resource not found in "
+ b.getSymbolicName() + ": " + bundleId;
Utils.logError(message, false);
continue;
}
/* This piece of code is currently quite fragile and hacky, but works.
*
* First, we rely on Eclipse-BuddyPolicy declared on the simplexml framework
* to instantiate arbitrary classes (from sources and algorithms).
* This policy could be removed if we passed an explicit Persister
* with a strategy substituting the context class loader with the given
* Bundle's loadClass() call. I leave it for now.
*
* We use a custom resource locator that searches the contributing
* plugin for resources matching the included resource.
*/
try
{
final ProcessingComponentSuite suite = ProcessingComponentSuite
.deserialize(suiteResource, resourceLookup);
/*
* Remove invalid descriptors, cache icons.
*/
failed.addAll(suite.removeUnavailableComponents());
for (ProcessingComponentDescriptor d : suite.getComponents())
{
final String iconPath = d.getIconPath();
if (StringUtils.isEmpty(iconPath))
{
continue;
}
componentImages.put(d.getId(),
imageDescriptorFromPlugin(bundleId, iconPath));
}
suites.add(suite);
}
catch (Exception e)
{
// Skip errors, logging them.
Utils.logError("Failed to load suite extension.", e, false);
}
}
}
// Merge all available suites
final ArrayList<DocumentSourceDescriptor> sources = Lists.newArrayList();
final ArrayList<ProcessingComponentDescriptor> algorithms = Lists.newArrayList();
for (ProcessingComponentSuite s : suites)
{
sources.addAll(s.getSources());
algorithms.addAll(s.getAlgorithms());
}
this.componentSuite = new ProcessingComponentSuite(sources, algorithms);
// Extract and cache bindableDescriptors.
for (ProcessingComponentDescriptor pcd : componentSuite.getComponents())
{
try
{
final String id = pcd.getId();
BindableDescriptor bindableDescriptor = pcd.getBindableDescriptor();
bindableDescriptors.put(id, bindableDescriptor);
processingDescriptors.put(id, pcd);
}
catch (Exception e)
{
Utils.logError("Failed to extract descriptor from: "
+ pcd.getId(), e, false);
}
}
/*
* Log errors.
*/
if (!failed.isEmpty())
{
for (ProcessingComponentDescriptor d : failed)
{
getLog().log(
new Status(Status.ERROR, PLUGIN_ID,
"Plugin loading failure: " + d.getId()
+ " (" + d.getTitle() + ")"
+ "\n" + StringUtils.defaultIfEmpty(d.getInitializationFailure().getMessage(),
"(no message)"), d.getInitializationFailure()));
}
}
}
/**
* @return Return failed component descriptors, if any.
*/
public List<ProcessingComponentDescriptor> getFailed()
{
return failed;
}
/**
* Return a resource locator pointing to the user's workspace or
* <code>null</code> if not available.
*/
private IResourceLocator getWorkspaceResourceLocator()
{
final URL instanceLocation = Platform.getInstanceLocation().getURL();
if (instanceLocation == null)
{
// Issue a warning about read-only location.
Utils.logError("Instance location not available.", false);
return null;
}
if (!"file".equalsIgnoreCase(instanceLocation.getProtocol()))
{
// Issue a warning about read-only location.
Utils.logError("Instance location not a file URL: "
+ instanceLocation, false);
return null;
}
// Invalid URLs may fail when converting to an URI. If so, try brute-force approach.
Path workspacePath;
try {
workspacePath = Paths.get(instanceLocation.toURI());
} catch (URISyntaxException e) {
Utils.logError("Instance location URI unparseable via .toURI(): "
+ instanceLocation, false);
}
try {
// we know it's a file URL, so get the path directly.
workspacePath = new File(instanceLocation.getPath()).toPath();
} catch (Exception e) {
Utils.logError("Instance location URI couldn't be parsed: "
+ instanceLocation, e, false);
return null;
}
workspacePath = workspacePath.toAbsolutePath();
if (!Files.exists(workspacePath))
{
try {
Files.createDirectories(workspacePath);
} catch (IOException e) {
Utils.logError("Could not create workspace folder.", e, false);
return null;
}
}
if (!Files.exists(workspacePath))
{
// Issue a warning about read-only location.
Utils.logError("Instance location does not exist: " + workspacePath, false);
return null;
}
return new DirLocator(workspacePath);
}
/**
* @return Returns the plugin's instance preferences.
*/
public static IEclipsePreferences getPreferences()
{
return InstanceScope.INSTANCE.getNode(WorkbenchCorePlugin.PLUGIN_ID);
}
}