/**
* Copyright 2014 SAP AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spotter.eclipse.ui.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.lpe.common.config.ConfigParameterDescription;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.eclipse.ui.Activator;
import org.spotter.eclipse.ui.ProjectNature;
import org.spotter.eclipse.ui.ServiceClientWrapper;
import org.spotter.eclipse.ui.UICoreException;
import org.spotter.eclipse.ui.editors.AbstractSpotterEditorInput;
import org.spotter.eclipse.ui.model.xml.MeasurementEnvironmentFactory;
import org.spotter.eclipse.ui.view.ResultsView;
import org.spotter.shared.configuration.ConfigKeys;
import org.spotter.shared.configuration.FileManager;
import org.spotter.shared.environment.model.XMeasurementEnvironment;
import org.spotter.shared.hierarchy.model.XPerformanceProblem;
/**
* An utility class to support project management for DynamicSpotter projects.
*
* @author Denis Knoepfle
*
*/
public final class SpotterProjectSupport {
private static final boolean DEFAULT_EXPERT_VIEW_ENABLED = false;
private static final String KEY_EXPERT_VIEW_ENABLED = "spotter.expertview.enabled";
private static final String ERR_GET_SPOTTER_CONFIG_PARAMS = "Error occured while getting DynamicSpotter configuration parameters";
private static final String ERR_CLOSE_STREAM = "Error occured while closing a stream";
private static final String ERR_CREATE_PROJECT = "Error occured while creating a new project";
private static final String ERR_LOAD_PROPERTIES = "Error occured while loading properties from file";
private static final String ERR_WRITE_ENV_XML = "Error occured while writing to environment XML file";
private static final String ERR_WRITE_HIERARCHY_XML = "Error occured while writing to hierarchy XML file";
private static final String ERR_WRITE_SPOTTER_CONF = "Error occured while writing to DynamicSpotter configuration file";
private static final Logger LOGGER = LoggerFactory.getLogger(SpotterProjectSupport.class);
// contains all project specific constant parameters
private static final String[] ALL_KEYS = { ConfigKeys.CONF_PROBLEM_HIERARCHY_FILE,
ConfigKeys.MEASUREMENT_ENVIRONMENT_FILE, ConfigKeys.RESULT_DIR };
private SpotterProjectSupport() {
}
/**
* Create a new DynamicSpotter project.
*
* @param projectName
* the name of the new project
* @param location
* the location of the new project or <code>null</code> for
* default location
* @return the newly created project or <code>null</code> on failure
*/
public static IProject createProject(String projectName, URI location) {
IProject project;
try {
project = createBaseProject(projectName, location);
addNature(project);
addEnvironmentXMLConfiguration(project);
addHierarchyXMLConfiguration(project);
addSpotterConfig(project);
String[] paths = { FileManager.DEFAULT_RESULTS_DIR_NAME };
addToProjectStructure(project, paths);
} catch (Exception e) {
// project creation failed
LOGGER.error(ERR_CREATE_PROJECT, e);
DialogUtils.handleError(ERR_CREATE_PROJECT, e);
project = null;
}
return project;
}
/**
* Delete the given project entirely from the workspace and from disk.
*
* @param project
* The project to delete
* @throws CoreException
* if deletion fails
*/
public static void deleteProject(IProject project) throws CoreException {
// close or reset open views and editors that refer to the project first
for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
for (IWorkbenchPage page : window.getPages()) {
// reset results view for the deleted project
ResultsView.reset(project);
// close DynamicSpotter editors
List<IEditorReference> closeEditors = new ArrayList<IEditorReference>();
for (IEditorReference ref : page.getEditorReferences()) {
IEditorPart editorPart = ref.getEditor(true);
if (editorPart != null && editorPart.getEditorInput() instanceof AbstractSpotterEditorInput) {
AbstractSpotterEditorInput input = (AbstractSpotterEditorInput) editorPart.getEditorInput();
if (project.equals(input.getProject())) {
closeEditors.add(ref);
}
}
}
page.closeEditors(closeEditors.toArray(new IEditorReference[closeEditors.size()]), false);
}
}
// deletes project completely from disk
// TODO: adjust dialog to allow soft deletion only from workspace
// without deleting it from disk
project.delete(true, true, null);
String projectName = project.getName();
Activator.getDefault().getNavigatorContentProvider().removeCachedProject(projectName);
deleteProjectPreferences(projectName);
}
/**
* Save the given environment to the specified file.
*
* @param file
* the destination file
* @param env
* the measurement environment to save
* @throws UICoreException
* when saving the environment fails
*/
public static void saveEnvironment(IFile file, XMeasurementEnvironment env) throws UICoreException {
try {
SpotterUtils.writeElementToFile(file, env);
} catch (Exception e) {
throw new UICoreException(ERR_WRITE_ENV_XML, e);
}
}
/**
* Save the given performance problem hierarchy to the specified file.
*
* @param file
* the destination file
* @param problem
* the performance problem hierarchy to save
* @throws UICoreException
* when saving fails
*/
public static void saveHierarchy(IFile file, XPerformanceProblem problem) throws UICoreException {
try {
SpotterUtils.writeElementToFile(file, problem);
} catch (Exception e) {
throw new UICoreException(ERR_WRITE_HIERARCHY_XML, e);
}
}
/**
* Save the given DynamicSpotter properties to the specified file. The
* necessary general properties will be added automatically.
*
* @param file
* the destination file
* @param properties
* the DynamicSpotter properties to save
* @throws UICoreException
* when saving fails
*/
public static void saveSpotterConfig(IFile file, Properties properties) throws UICoreException {
IProject project = file.getProject();
String location = project.getLocation().toString();
Properties general = FileManager.getInstance().createGeneralSpotterProperties(location);
Map<String, String> descriptionMapping = createDescriptionMapping(project.getName(), properties);
String output = FileManager.getInstance().createSpotterConfigFileContent(descriptionMapping, general,
properties);
InputStream source = new ByteArrayInputStream(output.getBytes());
try {
if (file.exists()) {
file.setContents(source, true, true, null);
} else {
file.create(source, true, null);
}
} catch (CoreException e) {
throw new UICoreException(ERR_WRITE_SPOTTER_CONF, e);
} finally {
try {
source.close();
} catch (IOException e) {
LOGGER.error(ERR_CLOSE_STREAM);
}
}
}
private static Map<String, String> createDescriptionMapping(String projectName, Properties properties) {
Map<String, String> descriptionMapping = new HashMap<>();
ServiceClientWrapper client = Activator.getDefault().getClient(projectName);
if (!client.testConnection(false)) {
return descriptionMapping;
}
for (String key : properties.stringPropertyNames()) {
ConfigParameterDescription desc = client.getSpotterConfigParam(key);
String comment = desc == null ? null : desc.getDescription();
descriptionMapping.put(key, comment);
}
return descriptionMapping;
}
/**
* Updates the DynamicSpotter configuration file. Loads the current
* configuration from file and saves it updating project related paths.
*
* @param project
* the project the DynamicSpotter configuration belongs to
* @throws UICoreException
* when updating fails
*/
public static void updateSpotterConfig(IProject project) throws UICoreException {
IFile spotterFile = project.getFile(FileManager.SPOTTER_CONFIG_FILENAME);
saveSpotterConfig(spotterFile, getSpotterConfig(spotterFile));
}
/**
* Loads and returns properties from the given file or <code>null</code> if
* properties could not be loaded.
*
* @param file
* the file to load from
* @return the properties loaded from the given file or <code>null</code> if
* an error occurred
* @throws UICoreException
* when properties could not be loaded
*/
public static Properties loadPropertiesFile(IFile file) throws UICoreException {
Properties properties = null;
if (file.exists()) {
InputStream fileContents = null;
try {
if (!file.isSynchronized(IResource.DEPTH_ZERO)) {
file.refreshLocal(IResource.DEPTH_ZERO, null);
}
// will throw CoreException on failure
fileContents = file.getContents();
Properties tmpProp = new Properties();
// will throw IOException on failure
tmpProp.load(fileContents);
// successfully loaded properties
properties = tmpProp;
} catch (CoreException | IOException e) {
throw new UICoreException(ERR_LOAD_PROPERTIES, e);
}
if (fileContents != null) {
// close stream as it remains open after load operation
try {
fileContents.close();
} catch (IOException e) {
LOGGER.error(ERR_CLOSE_STREAM);
}
}
}
return properties;
}
/**
* Retrieves the DynamicSpotter configuration from file or creates default
* one. If the file does not exist a default properties object will be used
* instead. Removes general properties from the loaded properties. This
* method always returns a well initialized <code>Properties</code> object.
*
* @param file
* the source file
* @return the filtered properties loaded from file or a new
* <code>Properties</code> object initialized with default
* DynamicSpotter properties on failure
* @throws UICoreException
* when an error occurred during the lookup or creation of the
* DynamicSpotter Config
*/
public static Properties getSpotterConfig(IFile file) throws UICoreException {
Properties properties = loadPropertiesFile(file);
if (properties == null) {
return createDefaultSpotterProperties(file.getName());
}
// remove project specific constant values
for (String key : ALL_KEYS) {
properties.remove(key);
}
return properties;
}
/**
* Creates default DynyamicSpotter properties.
*
* @param projectName
* The name of the project the properties are retrieved for
* @return default DynamicSpotter properties
* @throws UICoreException
* when default DynamicSpotter properties could not be created
*/
public static Properties createDefaultSpotterProperties(String projectName) throws UICoreException {
ServiceClientWrapper client = Activator.getDefault().getClient(projectName);
Set<ConfigParameterDescription> parameters = client.getConfigurationParameters();
if (parameters == null) {
throw new UICoreException(ERR_GET_SPOTTER_CONFIG_PARAMS, null);
}
Properties properties = new Properties();
for (ConfigParameterDescription desc : parameters) {
if (desc.isMandatory() && desc.getName() != null) {
String val = desc.getDefaultValue() == null ? "" : desc.getDefaultValue();
properties.put(desc.getName(), val);
}
}
return properties;
}
/**
* Deletes project preferences for the given project in the plugin's
* preference scope if there exist any.
*
* @param projectName
* The name of the project whose preferences should be deleted
*/
public static void deleteProjectPreferences(String projectName) {
IEclipsePreferences pluginPrefsRoot = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
try {
if (pluginPrefsRoot.nodeExists(projectName)) {
pluginPrefsRoot.node(projectName).removeNode();
pluginPrefsRoot.flush();
}
} catch (BackingStoreException e) {
LOGGER.error("Error during removal of project preferences.", e);
}
}
/**
* Returns the project preferences for the given project.
*
* @param projectName
* The name of the project whose preferences are requested
* @return the project preferences for the given project
*/
public static Preferences getProjectPreferences(String projectName) {
IEclipsePreferences pluginPrefsRoot = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
return pluginPrefsRoot.node(projectName);
}
/**
* Returns <code>true</code> if for the given project the expert view is
* currently enabled.
*
* @param projectName
* The name of the project
* @return <code>true</code> if enabled, otherwise <code>false</code>
*/
public static boolean isExpertViewEnabled(String projectName) {
Preferences prefs = getProjectPreferences(projectName);
boolean value = prefs.getBoolean(KEY_EXPERT_VIEW_ENABLED, DEFAULT_EXPERT_VIEW_ENABLED);
return value;
}
/**
* Set the expert view enabled state for the given project and tries to save
* the settings.
*
* @param projectName
* The name of the project
* @param enabled
* <code>true</code> for enabled, otherwise <code>false</code>
* @return <code>true</code> on success, otherwise <code>false</code>
*/
public static boolean setExpertModeEnabled(String projectName, boolean enabled) {
Preferences prefs = getProjectPreferences(projectName);
boolean oldValue = isExpertViewEnabled(projectName);
prefs.putBoolean(KEY_EXPERT_VIEW_ENABLED, enabled);
// force save
try {
prefs.flush();
return true;
} catch (BackingStoreException e) {
LOGGER.error("Saving expert mode enabled state failed.", e);
// restore old value
prefs.putBoolean(KEY_EXPERT_VIEW_ENABLED, oldValue);
DialogUtils.openWarning("Could not change the expert mode due to an error!");
return false;
}
}
/**
* Create a basic project.
*
* @param projectName
* name of the project
* @param location
* location of the project
* @return handle to the new project
* @throws UICoreException
* when base project could not be created
*/
private static IProject createBaseProject(String projectName, URI location) throws UICoreException {
IProject newProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (!newProject.exists()) {
URI projectLocation = location;
IProjectDescription desc = newProject.getWorkspace().newProjectDescription(newProject.getName());
if (location != null && ResourcesPlugin.getWorkspace().getRoot().getLocationURI().equals(location)) {
projectLocation = null;
}
desc.setLocationURI(projectLocation);
try {
newProject.create(desc, null);
if (!newProject.isOpen()) {
newProject.open(null);
}
} catch (CoreException e) {
throw new UICoreException(ERR_CREATE_PROJECT, e);
}
}
return newProject;
}
private static void createFolder(IFolder folder) throws CoreException {
IContainer parent = folder.getParent();
if (parent instanceof IFolder) {
createFolder((IFolder) parent);
}
if (!folder.exists()) {
folder.create(false, true, null);
}
}
/**
* Create a folder structure with a parent root.
*
* @param newProject
* the new project
* @param paths
* the paths that shall be created
* @throws CoreException
*/
private static void addToProjectStructure(IProject newProject, String[] paths) throws CoreException {
for (String path : paths) {
IFolder etcFolders = newProject.getFolder(path);
createFolder(etcFolders);
}
}
private static void addNature(IProject project) throws CoreException {
if (!project.hasNature(ProjectNature.NATURE_ID)) {
IProjectDescription description = project.getDescription();
String[] prevNatures = description.getNatureIds();
String[] newNatures = new String[prevNatures.length + 1];
System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
newNatures[prevNatures.length] = ProjectNature.NATURE_ID;
description.setNatureIds(newNatures);
IProgressMonitor monitor = null;
project.setDescription(description, monitor);
}
}
private static void addEnvironmentXMLConfiguration(IProject project) throws UICoreException {
XMeasurementEnvironment env = MeasurementEnvironmentFactory.getInstance().createMeasurementEnvironment();
saveEnvironment(project.getFile(FileManager.ENVIRONMENT_FILENAME), env);
}
private static void addHierarchyXMLConfiguration(IProject project) throws UICoreException {
XPerformanceProblem problem = Activator.getDefault().getClient(project.getName()).getDefaultHierarchy();
saveHierarchy(project.getFile(FileManager.HIERARCHY_FILENAME), problem);
}
private static void addSpotterConfig(IProject project) throws UICoreException {
saveSpotterConfig(project.getFile(FileManager.SPOTTER_CONFIG_FILENAME),
createDefaultSpotterProperties(project.getName()));
}
}