diff --git a/Kieker.WebGUI/README b/Kieker.WebGUI/README new file mode 100644 index 0000000000000000000000000000000000000000..234eaefcfaab63cacf4b4186a02fa323ce9a57b5 --- /dev/null +++ b/Kieker.WebGUI/README @@ -0,0 +1 @@ +In order to start the WebGUI use either "mvn jetty:run" or "mvn package" in combination with the corresponding script in the bin-directory. The GUI is available under "http://localhost:8080/Kieker.WebGUI/" after it has been started. \ No newline at end of file diff --git a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar index e1c53890f254d4bbc0f6c83f5811bbba6960533e..f3a97619cb3f4660770e5aa0466e3ef3cb8ebe40 100644 Binary files a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar and b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar differ diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentWorkSpaceProjectBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentWorkSpaceProjectBean.java index 360accae41dbc5a5c23af12148ff62690953ec70..618cdab97ec78846929ba6c428f5e88a9a2a72d3 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentWorkSpaceProjectBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/session/CurrentWorkSpaceProjectBean.java @@ -25,7 +25,9 @@ import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.faces.application.FacesMessage; import javax.faces.application.FacesMessage.Severity; @@ -35,7 +37,14 @@ import javax.faces.context.FacesContext; import kieker.analysis.model.analysisMetaModel.MIAnalysisMetaModelFactory; import kieker.analysis.model.analysisMetaModel.MIDependency; +import kieker.analysis.model.analysisMetaModel.MIFilter; +import kieker.analysis.model.analysisMetaModel.MIInputPort; +import kieker.analysis.model.analysisMetaModel.MIOutputPort; +import kieker.analysis.model.analysisMetaModel.MIPlugin; import kieker.analysis.model.analysisMetaModel.MIProject; +import kieker.analysis.model.analysisMetaModel.MIProperty; +import kieker.analysis.model.analysisMetaModel.MIRepository; +import kieker.analysis.model.analysisMetaModel.MIRepositoryConnector; import kieker.analysis.model.analysisMetaModel.impl.MAnalysisMetaModelFactory; import kieker.analysis.plugin.AbstractPlugin; import kieker.analysis.plugin.annotation.Plugin; @@ -43,10 +52,12 @@ import kieker.analysis.plugin.filter.AbstractFilterPlugin; import kieker.analysis.plugin.reader.AbstractReaderPlugin; import kieker.analysis.repository.AbstractRepository; import kieker.analysis.repository.annotation.Repository; +import kieker.common.configuration.Configuration; import kieker.webgui.common.FSManager; import kieker.webgui.common.Pair; import kieker.webgui.common.PluginFinder; import kieker.webgui.common.exception.LibraryAlreadyExistingException; +import kieker.webgui.common.exception.NewerProjectException; import org.primefaces.event.FileUploadEvent; import org.primefaces.model.UploadedFile; @@ -204,6 +215,9 @@ public final class CurrentWorkSpaceProjectBean { */ private void addLibrariesToModel() { final List<MIDependency> libs = FSManager.getInstance().getModelLibraries(this.projectName); + // Add them, but remove all existing dependencies so far to avoid double entries. This also makes sure that the model - after it has been opened - points + // just to valid dependencies (and to all of them). + this.project.getDependencies().clear(); this.project.getDependencies().addAll(libs); } @@ -248,6 +262,7 @@ public final class CurrentWorkSpaceProjectBean { public String clearProject() { this.project = null; this.projectName = null; + this.classLoader = null; this.timeStamp = 0; return CurrentWorkSpaceProjectBean.PAGE_PROJECT_OVERVIEW; @@ -295,8 +310,9 @@ public final class CurrentWorkSpaceProjectBean { CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_INFO, "Libary uploaded."); // As it seem to have worked, we can add the library to our model. this.project.getDependencies().add(lib); - // Update our class loader + // Update our class loader and the available plugins & repositories this.reloadClassLoader(); + this.addToToolPalette(lib); } catch (final LibraryAlreadyExistingException ex) { CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_WARN, "A library with the same name exists already."); } catch (final IOException ex) { @@ -341,6 +357,185 @@ public final class CurrentWorkSpaceProjectBean { return this.availableRepositories; } + /** + * This method tries to save the current project and informs the user about success or fail. + * + * @param overwriteNewerProject + * This flag determines whether a newer project should be overwritten. + */ + public void saveProject(final boolean overwriteNewerProject) { + try { + FSManager.getInstance().saveProject(this.projectName, this.project, this.timeStamp, overwriteNewerProject); + CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_INFO, "Project saved."); + // Update the time stamp! + this.resetTimeStamp(); + } catch (final IOException ex) { + CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while saving the projct."); + } catch (final NewerProjectException ex) { + CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_WARN, "The project has been modified externally in the meanwhile."); + } + } + + /** + * This method fills the ports of the given plugin. In other words: It tries to instantiate the given class and to extract the ports. If the instantiation fails + * (for various reasons), the method informs the user and executes normally - without an exception. + * + * @param clazz + * The class to be used as a base. + * @param plugin + * The plugin to be filled. + */ + private void fillPorts(final Class<AbstractPlugin> clazz, final MIPlugin plugin) { + try { + // Try to instantiate the given class, using the special constructor of Kieker. + final AbstractPlugin pluginInstance = clazz.getConstructor(Configuration.class).newInstance(new Configuration()); + + // Get the port and use them to initialize the model plugin. + final String[] inputPortNames = pluginInstance.getAllInputPortNames(); + final String[] outputPortNames = pluginInstance.getAllOutputPortNames(); + final String[] repositoryPortNames = pluginInstance.getAllRepositoryPortNames(); + + // Add input ports + if (plugin instanceof MIFilter) { + for (final String inputPortName : inputPortNames) { + final MIInputPort mInputPort = this.factory.createInputPort(); + mInputPort.setName(inputPortName); + mInputPort.setParent((MIFilter) plugin); + } + } + + // Add output ports. + for (final String outputPortName : outputPortNames) { + final MIOutputPort mOutputPort = this.factory.createOutputPort(); + mOutputPort.setName(outputPortName); + mOutputPort.setParent(plugin); + } + + // Add repository ports. + for (final String repositoryPortName : repositoryPortNames) { + final MIRepositoryConnector mConnector = this.factory.createRepositoryConnector(); + mConnector.setName(repositoryPortName); + plugin.getRepositories().add(mConnector); + } + + } catch (final Exception ex) { + // Inform the user about the fail! + CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_ERROR, "An errcor occured while loading the ports of the plugin."); + } + } + + /** + * This method fills the properties of the given repository. In other words: It tries to instantiate the given class and to extract the configuration keys. If + * the instantiation fails (for various reasons), the method informs the user and executes normally - without an exception. + * + * @param clazz + * The class to be used as a base. + * @param repository + * The repository to be filled. + */ + private void fillProperties(final Class<AbstractRepository> clazz, final MIRepository repository) { + try { + // Try to instantiate the given class, using the special constructor of Kieker. + final AbstractRepository repositoryInstance = clazz.getConstructor(Configuration.class).newInstance(new Configuration()); + // Get the current configuration and use it to initialize the model repository, as THIS configuration instance will contain all keys. + final Configuration configuration = repositoryInstance.getCurrentConfiguration(); + + repository.getProperties().addAll(this.extractProperties(configuration)); + } catch (final Exception ex) { + // Inform the user about the fail! + CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_ERROR, "An errcor occured while loading the properties of the repository."); + } + } + + /** + * This method fills the properties of the given plugin. In other words: It tries to instantiate the given class and to extract the configuration keys. If + * the instantiation fails (for various reasons), the method informs the user and executes normally - without an exception. + * + * @param clazz + * The class to be used as a base. + * @param plugin + * The plugin to be filled. + */ + private void fillProperties(final Class<AbstractPlugin> clazz, final MIPlugin plugin) { + try { + // Try to instantiate the given class, using the special constructor of Kieker. + final AbstractPlugin pluginInstance = clazz.getConstructor(Configuration.class).newInstance(new Configuration()); + // Get the current configuration and use it to initialize the model plugin, as THIS configuration instance will contain all keys. + final Configuration configuration = pluginInstance.getCurrentConfiguration(); + + plugin.getProperties().addAll(this.extractProperties(configuration)); + } catch (final Exception ex) { + // Inform the user about the fail! + CurrentWorkSpaceProjectBean.showMessage(FacesMessage.SEVERITY_ERROR, "An errcor occured while loading the properties of the plugin."); + } + } + + /** + * This method extracts the properties from the given configuration. In other words: For every key within the configuration, the method creates a model + * counterpart. + * + * @param configuration + * The configuration to be used for extraction. + * @return The list containing one {@link MIProperty} for every key within the configuration. + */ + private List<MIProperty> extractProperties(final Configuration configuration) { + final List<MIProperty> result = new ArrayList<MIProperty>(); + + // Run through all entries. + final Iterator<Map.Entry<Object, Object>> iterator = configuration.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry<Object, Object> entry = iterator.next(); + // Create a property object for the current entry. + final MIProperty property = this.factory.createProperty(); + property.setName(entry.getKey().toString()); + property.setValue(entry.getValue().toString()); + + result.add(property); + } + + return result; + } + + /** + * This method adds a new repository to the current model, using the given instance of {@code Class} for it. + * + * @param clazz + * The class of the repository to be added. + */ + public void addRepository(final Class<AbstractRepository> clazz) { + // Create a new instance for the model + final MIRepository repository = this.factory.createRepository(); + repository.setClassname(clazz.getName()); + repository.setName(clazz.getSimpleName()); + + this.fillProperties(clazz, repository); + // Add it to the project + this.project.getRepositories().add(repository); + } + + /** + * This method adds a new plugin to the current model, using the given instance of {@code Class} for it. + * + * @param clazz + * The class of the plugin to be added. This can be both, a filter or a reader. + */ + public void addPlugin(final Class<AbstractPlugin> clazz) { + // Create a new instance for the model + final MIPlugin plugin; + if (AbstractReaderPlugin.class.isAssignableFrom(clazz)) { + plugin = this.factory.createReader(); + } else { + plugin = this.factory.createFilter(); + } + plugin.setClassname(clazz.getName()); + plugin.setName(clazz.getSimpleName()); + + this.fillProperties(clazz, plugin); + this.fillPorts(clazz, plugin); + // Add it to the project + this.project.getPlugins().add(plugin); + } + /** * This method shows the current user a message by using the growl-component of PrimeFaces. * diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java index 47c3f517a5b93026c7eaa89be586b196fe067a2e..02d116a7531c215bdb6cec9b8e74b2b58ed8d8ac 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/FSManager.java @@ -46,6 +46,7 @@ import kieker.analysis.model.analysisMetaModel.impl.MAnalysisMetaModelFactory; import kieker.common.logging.Log; import kieker.common.logging.LogFactory; import kieker.webgui.common.exception.LibraryAlreadyExistingException; +import kieker.webgui.common.exception.NewerProjectException; import kieker.webgui.common.exception.ProjectAlreadyExistingException; import org.primefaces.model.UploadedFile; @@ -199,6 +200,42 @@ public final class FSManager { } } + /** + * This method tries to save the given project. + * + * @param projectName + * The name of the project to be saved. + * @param project + * The project to be saved. + * @param timeStamp + * The time stamp, the given project has been opened (necessary to check for a newer version). + * @param overwriteNewerProject + * This flag determines whether a newer project should be overwritten. + * @throws IOException + * If something went wrong during saving the project. + * @throws NewerProjectException + * This exception is raised if the time stamp of the project on the file system is newer than the given one <b>and</b> if the flag + * {@code overwriteNewerProject} is false. If it is true, a newer version will be overwritten. + */ + public void saveProject(final String projectName, final MIProject project, final long timeStamp, final boolean overwriteNewerProject) throws IOException, + NewerProjectException { + // Get the lock + final Object lock = this.getLock(projectName); + synchronized (lock) { + // Check for a newer version first + final long currTimeStamp = this.getCurrTimeStamp(projectName); + if (!overwriteNewerProject && (currTimeStamp > timeStamp)) { + throw new NewerProjectException("The project with the name '" + projectName + "' has a newer version on the FS."); + } + + // Everything seems to be okay (or we should overwrite the current file). Try to save it. Assemble the path to the KAX-file for this purpose. + final File kaxFile = new File(FSManager.ROOT_DIRECTORY + File.separator + projectName + File.separator + projectName + "." + FSManager.KAX_EXTENSION); + + // Try to save it. + AnalysisController.saveToFile(kaxFile, project); + } + } + /** * Returns a list containing all available projects on the FS as a string. * @@ -317,7 +354,7 @@ public final class FSManager { /** * This method delivers all available libraries for the given project as a pair of strings. The first element is the name of the library, the second one the size - * of the lib in MiBytes and as a human readable string. + * of the lib in MiBytes and as a human readable string. If the given name is null, an empty list will be returned. * * @param projectName * The name of the project. @@ -325,21 +362,23 @@ public final class FSManager { */ public List<Pair<String, String>> getLibraries(final String projectName) { final List<Pair<String, String>> result = new ArrayList<Pair<String, String>>(); - // Get the lock for the project - final Object lock = this.getLock(projectName); - - synchronized (lock) { - // Run through the libs and put them into our list. - final File libDir = new File(FSManager.ROOT_DIRECTORY + File.separator + projectName + File.separator + FSManager.LIB_DIRECTORY); - final File files[] = libDir.listFiles(); - if (files != null) { - for (final File file : files) { - if (file.getName().endsWith("." + FSManager.LIB_EXTENSION)) { - final float len = file.length() / 1024.0f / 1024.0f; - final NumberFormat numberFormat = new DecimalFormat("0.00"); - numberFormat.setRoundingMode(RoundingMode.DOWN); - final String lenStr = numberFormat.format(len); - result.add(new Pair<String, String>(file.getName(), lenStr)); + if (projectName != null) { + // Get the lock for the project + final Object lock = this.getLock(projectName); + + synchronized (lock) { + // Run through the libs and put them into our list. + final File libDir = new File(FSManager.ROOT_DIRECTORY + File.separator + projectName + File.separator + FSManager.LIB_DIRECTORY); + final File files[] = libDir.listFiles(); + if (files != null) { + for (final File file : files) { + if (file.getName().endsWith("." + FSManager.LIB_EXTENSION)) { + final float len = file.length() / 1024.0f / 1024.0f; + final NumberFormat numberFormat = new DecimalFormat("0.00"); + numberFormat.setRoundingMode(RoundingMode.DOWN); + final String lenStr = numberFormat.format(len); + result.add(new Pair<String, String>(file.getName(), lenStr)); + } } } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/NewerProjectException.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/NewerProjectException.java new file mode 100644 index 0000000000000000000000000000000000000000..552be8bd87279bb20ef4c016c7d2d01eeea001a2 --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/NewerProjectException.java @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright 2012 by + * + Christian-Albrechts-University of Kiel + * + Department of Computer Science + * + Software Engineering Group + * and others. + * + * 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 kieker.webgui.common.exception; + +/** + * This exception shows that an attempt of saving a project has failed, because a newer version is available. In other words: The project has been modified in the + * meanwhile. + * + * @author Nils Christian Ehmke + * @version 1.0 + */ +public class NewerProjectException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Creates a new instance of this class. + */ + public NewerProjectException() { + // No code necessary + } + + /** + * Creates a new instance of this class using the given parameters. + * + * @param msg + * The message used for the exception. + */ + public NewerProjectException(final String msg) { + super(msg); + } +} diff --git a/Kieker.WebGUI/src/main/webapp/ProjectWorkSpace.xhtml b/Kieker.WebGUI/src/main/webapp/ProjectWorkSpace.xhtml index 8840130f1b086f6db96e4fdaa5aa7a0a80e825b1..a04156a80fea52f8ef1a52649b54422279c4e8d7 100644 --- a/Kieker.WebGUI/src/main/webapp/ProjectWorkSpace.xhtml +++ b/Kieker.WebGUI/src/main/webapp/ProjectWorkSpace.xhtml @@ -22,7 +22,10 @@ <h:form> <p:menubar> <p:submenu label="File"> - <p:menuitem value="Save Project" ajax="true" disabled="#{empty currentWorkSpaceProjectBean.project}"/> + <p:menuitem value="Save Project" update=":messages" ajax="true" action="#{currentWorkSpaceProjectBean.saveProject(false)}" disabled="#{empty currentWorkSpaceProjectBean.project}"/> + <p:menuitem value="Save Project As" update=":messages" ajax="true" disabled="#{empty currentWorkSpaceProjectBean.project}"/> + <p:menuitem styleClass="Force-Save-Project-Button" value="Force Save Project" update=":messages" ajax="true" action="#{currentWorkSpaceProjectBean.saveProject(true)}" disabled="#{empty currentWorkSpaceProjectBean.project}"/> + <p:separator/> <p:menuitem value="Reset Project" ajax="true" disabled="#{empty currentWorkSpaceProjectBean.project}"/> <p:separator/> <p:menuitem value="Manage Libraries" onclick="manageLibrariesDialog.show()" ajax="true" disabled="#{empty currentWorkSpaceProjectBean.project}"/> @@ -75,7 +78,8 @@ </p:tab> <p:tab title="Repositories"> <ui:repeat value="#{currentWorkSpaceProjectBean.availableRepositories}" var="repository"> - <p:commandLink value="#{repository.simpleName}" update=":centerForm"/><br/> + <p:commandLink id="repositoryLink" value="#{repository.simpleName}" action="#{currentWorkSpaceProjectBean.addRepository(repository)}" update=":centerForm"/><br/> + <p:tooltip style="font-size: 15px" for="repositoryLink" value="#{currentWorkSpaceProjectBean.getDescription(repository)}"/> </ui:repeat> </p:tab> </p:accordionPanel> diff --git a/Kieker.WebGUI/src/main/webapp/css/ProjectWorkSpace.css b/Kieker.WebGUI/src/main/webapp/css/ProjectWorkSpace.css index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dcb7112953fcc5128dc7f2b1a90b03d5e96168c9 100644 --- a/Kieker.WebGUI/src/main/webapp/css/ProjectWorkSpace.css +++ b/Kieker.WebGUI/src/main/webapp/css/ProjectWorkSpace.css @@ -0,0 +1,5 @@ +@charset "UTF-8"; + +.Force-Save-Project-Button { + white-space: nowrap +} \ No newline at end of file diff --git a/Kieker.WebGUI/src/main/webapp/dialogs/manageLibrariesDialog.xhtml b/Kieker.WebGUI/src/main/webapp/dialogs/manageLibrariesDialog.xhtml index 9e4df24417d2b9c70f62fbfec9def811bc98f2ad..a0cd9fcc82fe839676ec47e44a976c2a4c34f37a 100644 --- a/Kieker.WebGUI/src/main/webapp/dialogs/manageLibrariesDialog.xhtml +++ b/Kieker.WebGUI/src/main/webapp/dialogs/manageLibrariesDialog.xhtml @@ -33,7 +33,7 @@ <br /> <br /> <h:form enctype="multipart/form-data"> - <p:fileUpload auto="true" allowTypes="/(\.|\/)(jar)$/" sizeLimit="104857600" mode="advanced" fileUploadListener="#{currentWorkSpaceProjectBean.handleFileUpload}" update=":dependenciesForm :messages"/> + <p:fileUpload auto="true" allowTypes="/(\.|\/)(jar)$/" sizeLimit="104857600" mode="advanced" fileUploadListener="#{currentWorkSpaceProjectBean.handleFileUpload}" update=":dependenciesForm :messages :toolpalette"/> </h:form> </div> <hr/>