package rocks.inspectit.ui.rcp;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.ILogListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
import rocks.inspectit.shared.all.minlog.MinlogToSLF4JLogger;
import rocks.inspectit.shared.all.util.ResourcesPathResolver;
import rocks.inspectit.ui.rcp.ci.InspectITConfigurationInterfaceManager;
import rocks.inspectit.ui.rcp.log.LogListener;
import rocks.inspectit.ui.rcp.repository.CmrRepositoryManager;
import rocks.inspectit.ui.rcp.storage.InspectITStorageManager;
import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J;
/**
* The main plugin class to be used in the desktop.
*/
public class InspectIT extends AbstractUIPlugin {
/**
* The id of this plugin.
*/
public static final String ID = "rocks.inspectit.ui.rcp";
/**
* Default name of the log file.
*/
private static final String DEFAULT_LOG_FILE_NAME = "logging-config.xml";
/**
* JVM property for the log file location.
*/
private static final String LOG_FILE_PROPERTY = "inspectit.logging.config";
/**
* The shared instance.
*/
private static InspectIT plugin;
/**
* The global repository management tool. It is used to create and save the connection to the
* CMR.
*/
private volatile CmrRepositoryManager cmrRepositoryManager;
/**
* Preferences store for the plug-in.
*/
private volatile ScopedPreferenceStore preferenceStore;
/**
* The global storage manager.
*/
private volatile InspectITStorageManager storageManager;
/**
* The global configuration interface manager.
*/
private volatile InspectITConfigurationInterfaceManager configurationInterfaceManager;
/**
* List of property change listener in the plug-in.
* <p>
* Currently the property change mechanism of Eclipse RCP is not used in inspectIT. However, it
* might be used in future.
*/
private List<IPropertyChangeListener> propertyChangeListeners = new ArrayList<>();
/**
* {@link ILogListener} used for logging.
*/
private ILogListener logListener;
/**
* This method is called upon plug-in activation.
*
* @param context
* the Context.
*
* @throws Exception
* in case of error.
*/
@Override
public void start(BundleContext context) throws Exception {
plugin = this;
initLogger();
// add log listener once logger is initialized
logListener = new LogListener();
Platform.addLogListener(logListener);
super.start(context);
}
/**
* Initializes the logger.
*/
private void initLogger() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
context.reset();
InputStream is = null;
try {
// first check if it's supplied as parameter
String logFileLocation = System.getProperty(LOG_FILE_PROPERTY);
if (null != logFileLocation) {
Path logPath = Paths.get(logFileLocation).toAbsolutePath();
if (Files.exists(logPath)) {
is = Files.newInputStream(logPath, StandardOpenOption.READ);
}
}
// then fail to default if none is specified
if (null == is) {
Path logPath = ResourcesPathResolver.getResourceFile(DEFAULT_LOG_FILE_NAME, Platform.getLocation().toFile()).toPath().toAbsolutePath();
if (Files.exists(logPath)) {
is = Files.newInputStream(logPath, StandardOpenOption.READ);
}
}
if (null != is) {
try {
configurator.doConfigure(is);
} catch (JoranException e) { // NOPMD NOCHK StatusPrinter will handle this
} finally {
is.close();
}
}
} catch (IOException e) { // NOPMD NOCHK StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
// use sysout-over-slf4j to redirect out and err calls to logger
SysOutOverSLF4J.sendSystemOutAndErrToSLF4J();
// initialize out minlog bridge to the slf4j
MinlogToSLF4JLogger.init();
}
/**
* This method is called when the plug-in is stopped.
*
* @param context
* the Context.
*
* @throws Exception
* in case of error.
*/
@Override
public void stop(BundleContext context) throws Exception {
if (null != cmrRepositoryManager) {
cmrRepositoryManager.cancelAllUpdateRepositoriesJobs();
}
// remove log listener
Platform.removeLogListener(logListener);
logListener = null; // NOPMD
super.stop(context);
plugin = null; // NOPMD
}
/**
* Returns the shared instance.
*
* @return Returns the shared instance.
*/
public static InspectIT getDefault() {
return plugin;
}
/**
* Registers the {@link IPropertyChangeListener} with the plug-in. Has no effect if the listener
* is already registered.
*
* @param propertyChangeListener
* {@link IPropertyChangeListener} to add.
*/
public void addPropertyChangeListener(IPropertyChangeListener propertyChangeListener) {
if (!propertyChangeListeners.contains(propertyChangeListener)) {
propertyChangeListeners.add(propertyChangeListener);
}
}
/**
* Unregisters the {@link IPropertyChangeListener} from the plug-in.
*
* @param propertyChangeListener
* {@link IPropertyChangeListener} to remove.
*/
public void removePropertyChangeListener(IPropertyChangeListener propertyChangeListener) {
propertyChangeListeners.remove(propertyChangeListener);
}
/**
* Delegates the {@link PropertyChangeEvent} to all listeners.
*
* @param event
* Event to delegate.
*/
public void firePropertyChangeEvent(PropertyChangeEvent event) {
for (IPropertyChangeListener listener : propertyChangeListeners) {
listener.propertyChange(event);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void initializeImageRegistry(ImageRegistry reg) {
Field[] allFields = InspectITImages.class.getFields();
for (Field field : allFields) {
if (field.getName().startsWith("IMG") && String.class.equals(field.getType())) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
String key = (String) field.get(null);
URL url = getBundle().getEntry(key);
if (null != reg.get(key)) {
// if we already have an icon with the same key continue
// can happen if two fields are pointing to same image
continue;
}
if (null != url) {
reg.put(key, ImageDescriptor.createFromURL(url));
// try to get the image in order to be certain given URL is representing an
// image file
reg.get(key);
} else {
// if image does not exists (url is null) show and log error
Status status = new Status(IStatus.ERROR, ID, "Image with the key '" + field.getName() + "' does not exist on the disk. ");
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, ID, "Error loading image with the key'" + field.getName() + "'. ");
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
continue;
}
}
}
}
/**
* Returns an image from the image registry by resolving the passed image key.
* <p>
* <b>Images retrieved by this method should not be disposed, because they are shared resources
* in the plugin and will be disposed with the disposal of the display.</b>
*
* @param imageKey
* The key of the image to look for in the registry.
* @return The generated image.
*/
public Image getImage(String imageKey) {
return getImageRegistry().get(imageKey);
}
/**
* Returns the image descriptor for the given key. The key can be one of the IMG_ definitions in
* {@link InspectITImages}.
* <p>
* <b>Every new image created with the given {@link ImageDescriptor} should be disposed by the
* caller.</b>
*
* @param imageKey
* The image key.
* @return The image descriptor for the given image key.
*/
public ImageDescriptor getImageDescriptor(String imageKey) {
return getImageRegistry().getDescriptor(imageKey);
}
/**
* Returns a service, if one is registered with the bundle context.
*
* @param clazz
* Class of service.
* @param <E>
* Type
* @return Service or <code>null</code> is service is not registered at the moment.
*/
public static <E> E getService(Class<E> clazz) {
ServiceReference<E> reference = getDefault().getBundle().getBundleContext().getServiceReference(clazz);
if (null != reference) {
return getDefault().getBundle().getBundleContext().getService(reference);
}
throw new RuntimeException("Requested service of the class " + clazz.getName() + " is not registered in the bundle.");
}
/**
* {@inheritDoc}
*/
@Override
public ScopedPreferenceStore getPreferenceStore() {
if (null == preferenceStore) {
synchronized (this) {
if (null == preferenceStore) { // NOCHK: DCL works with volatile.
preferenceStore = new ScopedPreferenceStore(ConfigurationScope.INSTANCE, ID);
}
}
}
return preferenceStore;
}
/**
* @return Returns the CMR repository manager.
*/
public CmrRepositoryManager getCmrRepositoryManager() {
if (null == cmrRepositoryManager) {
synchronized (this) {
if (null == cmrRepositoryManager) { // NOCHK: DCL works with volatile.
cmrRepositoryManager = new CmrRepositoryManager();
}
}
}
return cmrRepositoryManager;
}
/**
*
* @return Returns the {@link InspectITStorageManager}.
*/
public InspectITStorageManager getInspectITStorageManager() {
if (null == storageManager) {
synchronized (this) {
if (null == storageManager) { // NOCHK: DCL works with volatile.
storageManager = getService(InspectITStorageManager.class);
storageManager.startUp();
}
}
}
return storageManager;
}
/**
*
* @return Returns the {@link InspectITConfigurationInterfaceManager}.
*/
public InspectITConfigurationInterfaceManager getInspectITConfigurationInterfaceManager() {
if (null == configurationInterfaceManager) {
synchronized (this) {
if (null == configurationInterfaceManager) { // NOCHK: DCL works with volatile.
configurationInterfaceManager = getService(InspectITConfigurationInterfaceManager.class);
}
}
}
return configurationInterfaceManager;
}
/**
* Creates a simple error dialog.
*
* @param message
* The message of the dialog.
* @param throwable
* The exception to display
* @param code
* The code of the error. <b>-1</b> is a marker that the code has to be added later.
*/
public void createErrorDialog(String message, Throwable throwable, int code) {
IStatus status = new Status(IStatus.ERROR, ID, code, message, throwable);
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
/**
* Creates a simple error dialog without exception.
*
* @param message
* The message of the dialog.
* @param code
* The code of the error. <b>-1</b> is a marker that the code has to be added later.
*/
public void createErrorDialog(String message, int code) {
// Status sets exception to <code>null</code> internally.
IStatus status = new Status(IStatus.ERROR, ID, code, message, null);
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
/**
* Creates a simple info dialog.
*
* @param message
* The message of the dialog.
* @param code
* The code of the error. <b>-1</b> is a marker that the code has to be added later.
*/
public void createInfoDialog(String message, int code) {
MessageDialog.openInformation(null, "Information", message);
}
/**
* Logs the message with given severity. Logging only means no dialog will be displayed to the
* user. Severity can be {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#ERROR}
* which will define log level for the logger.
*
* @param severity
* {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#ERROR}
* @param message
* Message to log.
*/
public void log(int severity, String message) {
log(severity, message, null);
}
/**
* Logs the message and throwbale with given severity. Logging only means no dialog will be
* displayed to the user. Severity can be {@link IStatus#INFO}, {@link IStatus#WARNING} or
* {@link IStatus#ERROR} which will define log level for the logger.
*
* @param severity
* {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#ERROR}
* @param message
* Message to log.
* @param throwable
* Throwable to log.
*/
public void log(int severity, String message, Throwable throwable) {
IStatus status = new Status(severity, ID, 0, message, throwable);
StatusManager.getManager().handle(status, StatusManager.LOG);
}
}