diff --git a/Kieker.WebGUI/pom.xml b/Kieker.WebGUI/pom.xml index 328f387d021cf4036dad99c2e61e44c79f2aaf01..516c51daceff92e8d4141f663a98dfefeb91fd2d 100644 --- a/Kieker.WebGUI/pom.xml +++ b/Kieker.WebGUI/pom.xml @@ -339,7 +339,7 @@ </compilerArguments> </configuration> </plugin> - <!-- This is some kind of a hack, as maven doesn't want to include system jars into the war-file. We use ant instead. --> + <!-- This is some kind of a hack, as maven doesn't want to include system jars into the war-file. We use ant instead. --> <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java index f3930baa4d48a7741444233915331913014dc827..cf3b62efc71d86a91ca5b8d1c046fc045c8cbddf 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java @@ -21,6 +21,7 @@ package kieker.webgui.beans.view; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -39,6 +40,7 @@ import javax.faces.bean.ManagedProperty; import javax.faces.bean.ViewScoped; import javax.faces.context.FacesContext; +import kieker.analysis.display.annotation.Display; import kieker.analysis.model.analysisMetaModel.MIAnalysisMetaModelFactory; import kieker.analysis.model.analysisMetaModel.MIDependency; import kieker.analysis.model.analysisMetaModel.MIDisplay; @@ -71,6 +73,7 @@ import kieker.webgui.common.Pair; import kieker.webgui.common.PluginFinder; import kieker.webgui.common.exception.LibraryAlreadyExistingException; import kieker.webgui.common.exception.NewerProjectException; +import kieker.webgui.common.exception.ProjectLoadException; import org.primefaces.context.RequestContext; import org.primefaces.event.FileUploadEvent; @@ -145,6 +148,8 @@ public final class CurrentAnalysisEditorBean { @ManagedProperty(value = "#{currentAnalysisEditorGraphBean}") private CurrentAnalysisEditorGraphBean currentAnalysisEditorGraphBean; + private ClassAndMethodContainer classAndMethodContainer; + /** * Creates a new instance of this class. */ @@ -217,8 +222,10 @@ public final class CurrentAnalysisEditorBean { /** * This method initializes the bean by using the current project name to load the project. <b>Do not call this method manually. It will only be accessed by * JSF.</b> + * + * @throws ProjectLoadException */ - public void initialize() { + public void initialize() throws ProjectLoadException { synchronized (this) { // Make sure that the initialization will only be done for the init request. if (!FacesContext.getCurrentInstance().isPostback()) { @@ -227,8 +234,9 @@ public final class CurrentAnalysisEditorBean { if (this.project != null) { // Remember the current time! This is important for the later comparison of the time stamps. this.resetTimeStamp(); - // Update the class loader + // Update the class loader and the specific classes used within various methods in this bean this.reloadClassLoader(); + this.reloadClassesAndMethods(); // Add the libraries within the lib-folder to the current model this.addLibrariesToModel(); // Load the available readers, filters and repositories @@ -238,6 +246,10 @@ public final class CurrentAnalysisEditorBean { } } + private void reloadClassesAndMethods() throws ProjectLoadException { + this.classAndMethodContainer = new ClassAndMethodContainer(this.classLoader); + } + /** * This method loads the list of available readers, filters and repositories, using the current libraries within the model. */ @@ -288,31 +300,21 @@ public final class CurrentAnalysisEditorBean { * The library url used to load the plugins and repositories. */ private void addToToolPalette(final URL url) { - final Class<?> abstractFilterPluginClass; - final Class<?> abstractReaderPluginClass; - try { - abstractFilterPluginClass = this.classLoader.loadClass(AbstractFilterPlugin.class.getCanonicalName()); - abstractReaderPluginClass = this.classLoader.loadClass(AbstractReaderPlugin.class.getCanonicalName()); - } catch (final ClassNotFoundException ex) { - CurrentAnalysisEditorBean.LOG.error("Could not load class", ex); - return; - } - - final List<Class<AbstractRepository>> repositories = this.pluginFinder.getAllRepositoriesWithinJar(url); - final List<Class<AbstractPlugin>> plugins = this.pluginFinder.getAllPluginsWithinJar(url); + final List<Class<?>> repositories = this.pluginFinder.getAllRepositoriesWithinJar(url); + final List<Class<?>> plugins = this.pluginFinder.getAllPluginsWithinJar(url); // Now run through the available classes and add all non-abstract classes to our lists - for (final Class<AbstractRepository> repository : repositories) { + for (final Class<?> repository : repositories) { if (!Modifier.isAbstract(repository.getModifiers())) { this.availableRepositories.add(repository); } } - for (final Class<? extends AbstractPlugin> plugin : plugins) { + for (final Class<?> plugin : plugins) { if (!Modifier.isAbstract(plugin.getModifiers())) { // The following cast results in the unchecked-cast-warnings, but we know that the cast should be correct. - if (abstractFilterPluginClass.isAssignableFrom(plugin)) { + if (this.classAndMethodContainer.abstractFilterPluginClass.isAssignableFrom(plugin)) { this.availableFilters.add(plugin); } else { - if (abstractReaderPluginClass.isAssignableFrom(plugin)) { + if (this.classAndMethodContainer.abstractReaderPluginClass.isAssignableFrom(plugin)) { this.availableReaders.add(plugin); } } @@ -336,17 +338,20 @@ public final class CurrentAnalysisEditorBean { /** * This method reloads the class loader. In other words: The class loader will always be able to load classes from the jar-files within the lib-folder of the * project. + * + * @throws ProjectLoadException */ - private void reloadClassLoader() { + private void reloadClassLoader() throws ProjectLoadException { synchronized (this) { this.classLoader = FSManager.getInstance().getClassLoader(this.projectName); try { this.pluginFinder = new PluginFinder(this.classLoader); - } catch (final ClassNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } catch (final ClassNotFoundException ex) { + CurrentAnalysisEditorBean.LOG.error("Could not load classes.", ex); + throw new ProjectLoadException(); } catch (final NullPointerException ex) { - ex.printStackTrace(); + CurrentAnalysisEditorBean.LOG.error("Invalid class loader", ex); + throw new ProjectLoadException(); } } } @@ -391,48 +396,58 @@ public final class CurrentAnalysisEditorBean { * @return The description for the class or a substitute if none is available. This is in either case human readable. */ public String getDescription(final Class<?> clazz) { - // Get the two potential annotations - final Plugin annotationPlugin = clazz.getAnnotation(Plugin.class); - final Repository annotationRepository = clazz.getAnnotation(Repository.class); - - // Now check which one of them is available - if ((annotationPlugin == null) || annotationPlugin.description().isEmpty()) { - if ((annotationRepository == null) || annotationRepository.description().isEmpty()) { - // None. Deliver a human readable substitute. - return "No description available"; + try { + // Get the two potential annotations + final Annotation annotationPlugin = clazz.getAnnotation(this.classAndMethodContainer.pluginAnnotationClass); + final Annotation annotationRepository = clazz.getAnnotation(this.classAndMethodContainer.repositoryAnnotationClass); + + // Now check which one of them is available + if ((annotationPlugin == null) || ((String) this.classAndMethodContainer.pluginDescriptionMethod.invoke(annotationPlugin, new Object[0])).isEmpty()) { + if ((annotationRepository == null) + || ((String) this.classAndMethodContainer.repositoryDescriptionMethod.invoke(annotationRepository, new Object[0])).isEmpty()) { + // None. Deliver a human readable substitute. + return "No description available"; + } else { + return (String) this.classAndMethodContainer.repositoryDescriptionMethod.invoke(annotationRepository, new Object[0]); + } } else { - return annotationRepository.description(); + return (String) this.classAndMethodContainer.pluginDescriptionMethod.invoke(annotationPlugin, new Object[0]); } - } else { - return annotationPlugin.description(); + } catch (final ReflectiveOperationException ex) { + CurrentAnalysisEditorBean.LOG.warn("Could not invoke method", ex); + return "No description available"; } } - public List<Property> getProperties(final Class<?> clazz) { - final ArrayList<Property> result = new ArrayList<Property>(); + public List<Annotation> getProperties(final Class<?> clazz) { + final List<Annotation> result = new ArrayList<Annotation>(); - // Get the two potential annotations - final Plugin annotationPlugin = clazz.getAnnotation(Plugin.class); - final Repository annotationRepository = clazz.getAnnotation(Repository.class); + try { + // Get the two potential annotations + final Annotation annotationPlugin = clazz.getAnnotation(this.classAndMethodContainer.pluginAnnotationClass); + final Annotation annotationRepository = clazz.getAnnotation(this.classAndMethodContainer.repositoryAnnotationClass); - final Property[] properties; + final Annotation[] properties; - // Now check which one of them is available - if ((annotationPlugin == null) || annotationPlugin.description().isEmpty()) { - if ((annotationRepository == null) || annotationRepository.description().isEmpty()) { - // None. - properties = new Property[0]; + // Now check which one of them is available + if (annotationPlugin == null) { + if (annotationRepository == null) { + // None. + properties = new Property[0]; + } else { + properties = (Annotation[]) this.classAndMethodContainer.repositoryConfigurationMethod.invoke(annotationRepository, new Object[0]); + } } else { - properties = annotationRepository.configuration(); + properties = (Annotation[]) this.classAndMethodContainer.pluginConfigurationMethod.invoke(annotationPlugin, new Object[0]); } - } else { - properties = annotationPlugin.configuration(); - } - for (final Property property : properties) { - result.add(property); - } + for (final Annotation property : properties) { + result.add(property); + } + } catch (final ReflectiveOperationException ex) { + CurrentAnalysisEditorBean.LOG.warn("Could not invoke method", ex); + } return result; } @@ -443,12 +458,12 @@ public final class CurrentAnalysisEditorBean { * The class to be analyzed. * @return A list containing the available input ports. */ - public List<InputPort> getInputPorts(final Class<?> clazz) { - final ArrayList<InputPort> result = new ArrayList<InputPort>(); + public List<Annotation> getInputPorts(final Class<?> clazz) { + final List<Annotation> result = new ArrayList<Annotation>(); for (final Method method : clazz.getMethods()) { // Get the potential annotation - final InputPort annotationPort = method.getAnnotation(InputPort.class); + final Annotation annotationPort = method.getAnnotation(this.classAndMethodContainer.inputPortAnnotationClass); // Now check whether it is available if (annotationPort != null) { result.add(annotationPort); @@ -465,19 +480,23 @@ public final class CurrentAnalysisEditorBean { * The class to be analyzed. * @return A list containing the available output ports. */ - public List<OutputPort> getOutputPorts(final Class<?> clazz) { - final ArrayList<OutputPort> result = new ArrayList<OutputPort>(); + public List<Annotation> getOutputPorts(final Class<?> clazz) { + final List<Annotation> result = new ArrayList<Annotation>(); - // Get the potential annotation - final Plugin annotationPlugin = clazz.getAnnotation(Plugin.class); + try { + // Get the potential annotation + final Annotation annotationPlugin = clazz.getAnnotation(this.classAndMethodContainer.pluginAnnotationClass); - // Now check whether it is available - if (annotationPlugin != null) { - for (final OutputPort oPort : annotationPlugin.outputPorts()) { - result.add(oPort); + // Now check whether it is available + if (annotationPlugin != null) { + for (final Annotation oPort : (Annotation[]) this.classAndMethodContainer.pluginOutputPortsMethod.invoke(annotationPlugin, new Object[0])) { + result.add(oPort); + } } - } + } catch (final ReflectiveOperationException ex) { + CurrentAnalysisEditorBean.LOG.warn("Could not invoke method", ex); + } return result; } @@ -488,16 +507,33 @@ public final class CurrentAnalysisEditorBean { * The class to be analyzed. * @return A list containing the available repository ports. */ - public List<RepositoryPort> getRepositoryPorts(final Class<?> clazz) { - final ArrayList<RepositoryPort> result = new ArrayList<RepositoryPort>(); + public List<Annotation> getRepositoryPorts(final Class<?> clazz) { + final List<Annotation> result = new ArrayList<Annotation>(); + try { + // Get the potential annotation + final Annotation annotationPlugin = clazz.getAnnotation(this.classAndMethodContainer.pluginAnnotationClass); - // Get the potential annotation - final Plugin annotationPlugin = clazz.getAnnotation(Plugin.class); + // Now check whether it is available + if (annotationPlugin != null) { + for (final Annotation rPort : (Annotation[]) this.classAndMethodContainer.pluginRepositoryPortsMethod.invoke(annotationPlugin, new Object[0])) { + result.add(rPort); + } + } + } catch (final ReflectiveOperationException ex) { + CurrentAnalysisEditorBean.LOG.warn("Could not invoke method", ex); + } + return result; + } + + public List<Annotation> getDisplays(final Class<?> clazz) { + final List<Annotation> result = new ArrayList<Annotation>(); - // Now check whether it is available - if (annotationPlugin != null) { - for (final RepositoryPort rPort : annotationPlugin.repositoryPorts()) { - result.add(rPort); + for (final Method method : clazz.getMethods()) { + // Get the potential annotation + final Annotation display = method.getAnnotation(this.classAndMethodContainer.displayAnnotationClass); + // Now check whether it is available + if (display != null) { + result.add(display); } } @@ -509,8 +545,9 @@ public final class CurrentAnalysisEditorBean { * * @param event * The upload event. + * @throws ProjectLoadException */ - public void handleFileUpload(final FileUploadEvent event) { + public void handleFileUpload(final FileUploadEvent event) throws ProjectLoadException { // Get the file from the event final UploadedFile file = event.getFile(); @@ -612,33 +649,20 @@ public final class CurrentAnalysisEditorBean { private boolean fillDisplays(final Class<AbstractPlugin> clazz, final MIPlugin plugin) { synchronized (this) { 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 displays and convert them into model instances - final String[] displayNames = pluginInstance.getAllDisplayNames(); - for (final String displayName : displayNames) { + final List<Annotation> displays = this.getDisplays(clazz); + for (final Annotation display : displays) { final MIDisplay mDisplay = this.factory.createDisplay(); - mDisplay.setName(displayName); + mDisplay.setName((String) this.classAndMethodContainer.displayNameMethod.invoke(display, new Object[0])); plugin.getDisplays().add(mDisplay); } + return true; - } catch (final InstantiationException ex) { + } catch (final ReflectiveOperationException ex) { CurrentAnalysisEditorBean.LOG.error("An error occured while loading the displays of the plugin.", ex); CurrentAnalysisEditorBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the displays of the plugin."); return false; - } catch (final IllegalAccessException ex) { - CurrentAnalysisEditorBean.LOG.error("An error occured while loading the displays of the plugin.", ex); - CurrentAnalysisEditorBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the displays of the plugin."); - return false; - } catch (final InvocationTargetException ex) { - CurrentAnalysisEditorBean.LOG.error("An error occured while loading the displays of the plugin.", ex); - CurrentAnalysisEditorBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the displays of the plugin."); - return false; - } catch (final NoSuchMethodException ex) { - CurrentAnalysisEditorBean.LOG.error("An error occured while loading the displays of the plugin.", ex); - CurrentAnalysisEditorBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the displays of the plugin."); - return false; - } catch (final NoClassDefFoundError ex) { + } catch (final ClassCastException ex) { CurrentAnalysisEditorBean.LOG.error("An error occured while loading the displays of the plugin.", ex); CurrentAnalysisEditorBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the displays of the plugin."); return false; @@ -1028,10 +1052,101 @@ public final class CurrentAnalysisEditorBean { this.currentAnalysisEditorGraphBean.refreshGraph(); } - public void nodeSelected(final EObject selectedNode) { + public void nodeSelected(final EObject node) { + synchronized (this) { + this.selectedNode = node; + } + } + + public void nodeRemoved(final EObject node) { synchronized (this) { - this.selectedNode = selectedNode; + // Remove the component from the project + if (node instanceof MIPlugin) { + this.project.getPlugins().remove(node); + + // Remove the corresponding connections + final List<MIInputPort> toBeRemoved = new ArrayList<MIInputPort>(); + for (final MIPlugin plugin : this.project.getPlugins()) { + for (final MIOutputPort oPort : plugin.getOutputPorts()) { + toBeRemoved.clear(); + for (final MIInputPort iPort : oPort.getSubscribers()) { + if (iPort.getParent() == node) { + toBeRemoved.add(iPort); + } + } + oPort.getSubscribers().removeAll(toBeRemoved); + } + } + } else { + this.project.getRepositories().remove(node); + + // Remove the corresponding connections + for (final MIPlugin plugin : this.project.getPlugins()) { + for (final MIRepositoryConnector repoConn : plugin.getRepositories()) { + if (repoConn.getRepository() == node) { + repoConn.setRepository(null); + } + } + } + } + + // Unselect the currently selected node if it is the one which has just been removed + if (this.selectedNode == node) { + this.selectedNode = null; // NOPMD + } } } + public void edgeCreated(final EObject sourcePort, final EObject targetPort) { + ((MIOutputPort) sourcePort).getSubscribers().add((MIInputPort) targetPort); + } + + public void edgeRemoved(final EObject sourcePort, final EObject targetPort) { + ((MIOutputPort) sourcePort).getSubscribers().remove(targetPort); + } + + private static final class ClassAndMethodContainer { + + public final Class<?> abstractFilterPluginClass; + public final Class<?> abstractReaderPluginClass; + public final Class<? extends Annotation> pluginAnnotationClass; + public final Class<? extends Annotation> repositoryAnnotationClass; + public final Class<? extends Annotation> propertyAnnotationClass; + public final Class<? extends Annotation> outputPortAnnotationClass; + public final Class<? extends Annotation> inputPortAnnotationClass; + public final Class<? extends Annotation> repositoryPortAnnotationClass; + public final Class<? extends Annotation> displayAnnotationClass; + public final Method pluginDescriptionMethod; + public final Method repositoryDescriptionMethod; + public final Method pluginConfigurationMethod; + public final Method repositoryConfigurationMethod; + public final Method pluginOutputPortsMethod; + public final Method pluginRepositoryPortsMethod; + public final Method displayNameMethod; + + public ClassAndMethodContainer(final ClassLoader classLoader) throws ProjectLoadException { + try { + this.abstractFilterPluginClass = classLoader.loadClass(AbstractFilterPlugin.class.getCanonicalName()); + this.abstractReaderPluginClass = classLoader.loadClass(AbstractReaderPlugin.class.getCanonicalName()); + this.pluginAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(Plugin.class.getCanonicalName()); + this.repositoryAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(Repository.class.getCanonicalName()); + this.propertyAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(Property.class.getCanonicalName()); + this.outputPortAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(OutputPort.class.getCanonicalName()); + this.inputPortAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(InputPort.class.getCanonicalName()); + this.repositoryPortAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(RepositoryPort.class.getCanonicalName()); + this.displayAnnotationClass = (Class<? extends Annotation>) classLoader.loadClass(Display.class.getCanonicalName()); + + this.pluginDescriptionMethod = this.pluginAnnotationClass.getMethod("description", new Class<?>[0]); + this.repositoryDescriptionMethod = this.repositoryAnnotationClass.getMethod("description", new Class<?>[0]); + this.pluginConfigurationMethod = this.pluginAnnotationClass.getMethod("configuration", new Class<?>[0]); + this.repositoryConfigurationMethod = this.repositoryAnnotationClass.getMethod("configuration", new Class<?>[0]); + this.pluginOutputPortsMethod = this.pluginAnnotationClass.getMethod("outputPorts", new Class<?>[0]); + this.pluginRepositoryPortsMethod = this.pluginAnnotationClass.getMethod("repositoryPorts", new Class<?>[0]); + this.displayNameMethod = this.displayAnnotationClass.getMethod("name", new Class<?>[0]); + } catch (final ReflectiveOperationException ex) { + CurrentAnalysisEditorBean.LOG.error("An error occured while loading the classes and methods.", ex); + throw new ProjectLoadException(); + } + } + } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java index 3f676a8ef3f077c5333f2a73373f9258eac55680..9a6d9c94c79db6e3cbb5a384eff702bd31e47b7e 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java @@ -34,6 +34,8 @@ import kieker.analysis.model.analysisMetaModel.MIPort; import kieker.analysis.model.analysisMetaModel.MIReader; import kieker.analysis.model.analysisMetaModel.MIRepository; import kieker.analysis.model.analysisMetaModel.MIRepositoryConnector; +import kieker.common.logging.Log; +import kieker.common.logging.LogFactory; import kieker.monitoring.core.registry.Registry; import org.primefaces.context.RequestContext; @@ -53,6 +55,10 @@ import org.eclipse.emf.ecore.EObject; @ManagedBean @ViewScoped public class CurrentAnalysisEditorGraphBean { + /** + * This is the log for errors, exceptions etc. + */ + private static final Log LOG = LogFactory.getLog(CurrentAnalysisEditorGraphBean.class); /** * This is the javascript code to initialize the visual graph. */ @@ -61,6 +67,18 @@ public class CurrentAnalysisEditorGraphBean { * This is the javasscript code to add the click listener to the graph. */ private static final String JS_CMD_ADD_CLICK_LISTENER = "graph.addListener(\"onClick\", nodeClickListener);"; + /** + * This is the javasscript code to add the remove node listener to the graph. + */ + private static final String JS_CMD_ADD_REMOVE_NODE_LISTENER = "graph.addListener('onRemoveNode', nodeRemoveListener);"; + /** + * This is the javasscript code to add the create edge listener to the graph. + */ + private static final String JS_CMD_ADD_CREATE_EDGE_LISTENER = "graph.addListener('onCreateEdge', edgeCreateListener);"; + /** + * This is the javasscript code to add the remove edge listener to the graph. + */ + private static final String JS_CMD_ADD_REMOVE_EDGE_LISTENER = "graph.addListener('onRemoveEdge', edgeRemoveListener);"; /** * This is the javascript code for a node object. */ @@ -88,11 +106,15 @@ public class CurrentAnalysisEditorGraphBean { /** * This is the javascript code to add an edge to the graph. */ - private static final String JS_CMD_ADD_EDGE = "graph.addEdge(\"%s\", \"%s\");"; + private static final String JS_CMD_ADD_EDGE = "graph.addEdge(\"%s\", \"%s\", \"%s\");"; /** * This is the javascript code to redraw the command. */ private static final String JS_CMD_REFRESH_GRAPH = "graph.refresh();"; + /** + * This is the javascript code to rename a node within the graph. + */ + private static final String JS_CMD_RENAME_NODE = "graph.getNode(%s).name = '%s';"; /** * This map contains all components (plugins, repositories and ports) within the graph to identify them with a unique ID. */ @@ -113,6 +135,9 @@ public class CurrentAnalysisEditorGraphBean { public void initGraph() { RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_INIT_GRAPH); RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_CLICK_LISTENER); + RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REMOVE_NODE_LISTENER); + RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_CREATE_EDGE_LISTENER); + RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REMOVE_EDGE_LISTENER); } /** @@ -170,7 +195,7 @@ public class CurrentAnalysisEditorGraphBean { */ public void addConnection(final MIPlugin source, final MIPlugin destination, final MIOutputPort outputPort, final MIInputPort inputPort) { RequestContext.getCurrentInstance().execute(String.format(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_EDGE, this.assembleGraphPortID(source, outputPort), - this.assembleGraphPortID(destination, inputPort))); + this.assembleGraphPortID(destination, inputPort), "")); } /** @@ -256,7 +281,7 @@ public class CurrentAnalysisEditorGraphBean { * @return The ID for the port within the graph */ private String assembleGraphPortID(final MIPlugin plugin, final MIPort port) { - return this.componentMap.get(plugin) + "_" + this.componentMap.get(port); + return this.componentMap.get(plugin) + "." + this.componentMap.get(port); } /** @@ -295,12 +320,78 @@ public class CurrentAnalysisEditorGraphBean { final String clickedNodeID = paramMap.get("ID"); // Now search the correct node - final EObject selectedNode = this.componentMap.get(Integer.parseInt(clickedNodeID)); - this.currentAnalysisEditorBean.nodeSelected(selectedNode); + try { + final EObject selectedNode = this.componentMap.get(Integer.parseInt(clickedNodeID)); + if ((selectedNode != null) && (this.currentAnalysisEditorBean != null)) { + this.currentAnalysisEditorBean.nodeSelected(selectedNode); + } + } catch (final NumberFormatException ex) { + // Ignore an invalid ID, but log it. + CurrentAnalysisEditorGraphBean.LOG.warn("Invalid ID", ex); + } } + /** + * This is the action which can be called from the javascript code to show that a node has been removed. It informs the connected + * {@link CurrentAnalysisEditorBean} about this. + */ public void nodeRemoved() { + // Get the parameters + final Map<String, String> paramMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); + final String clickedNodeID = paramMap.get("ID"); + + // Now search the correct node + try { + final EObject selectedNode = this.componentMap.get(Integer.parseInt(clickedNodeID)); + if ((selectedNode != null) && (this.currentAnalysisEditorBean != null)) { + this.currentAnalysisEditorBean.nodeRemoved(selectedNode); + } + } catch (final NumberFormatException ex) { + // Ignore an invalid ID, but log it. + CurrentAnalysisEditorGraphBean.LOG.info("Invalid ID", ex); + } + } + + public void edgeCreated() { + // Get the parameters + final Map<String, String> paramMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); + + final String sourcePortID = paramMap.get("sourcePortID").split("\\.")[1]; + final String targetPortID = paramMap.get("targetPortID").split("\\.")[1]; + + // Now search the correct components + try { + final EObject sourcePort = this.componentMap.get(Integer.parseInt(sourcePortID)); + final EObject targetPort = this.componentMap.get(Integer.parseInt(targetPortID)); + + if ((sourcePort != null) && (targetPort != null) && (this.currentAnalysisEditorBean != null)) { + this.currentAnalysisEditorBean.edgeCreated(sourcePort, targetPort); + } + } catch (final NumberFormatException ex) { + // Ignore an invalid ID, but log it. + CurrentAnalysisEditorGraphBean.LOG.info("Invalid ID", ex); + } + } + + public void edgeRemoved() { + // Get the parameters + final Map<String, String> paramMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); + + final String sourcePortID = paramMap.get("sourcePortID").split("\\.")[1]; + final String targetPortID = paramMap.get("targetPortID").split("\\.")[1]; + // Now search the correct components + try { + final EObject sourcePort = this.componentMap.get(Integer.parseInt(sourcePortID)); + final EObject targetPort = this.componentMap.get(Integer.parseInt(targetPortID)); + + if ((sourcePort != null) && (targetPort != null) && (this.currentAnalysisEditorBean != null)) { + this.currentAnalysisEditorBean.edgeRemoved(sourcePort, targetPort); + } + } catch (final NumberFormatException ex) { + // Ignore an invalid ID, but log it. + CurrentAnalysisEditorGraphBean.LOG.info("Invalid ID", ex); + } } /** @@ -313,4 +404,17 @@ public class CurrentAnalysisEditorGraphBean { this.currentAnalysisEditorBean = currentAnalysisEditorBean; } + /** + * Renames a given node and repaints the graph. + * + * @param node + * The node to rename. + * @param newName + * The new name of the node. + */ + public void renameNode(final EObject node, final String newName) { + RequestContext.getCurrentInstance().execute(String.format(CurrentAnalysisEditorGraphBean.JS_CMD_RENAME_NODE, this.componentMap.get(node), newName)); + this.refreshGraph(); + } + } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java index 61bfd759c46625af4c9d100646cd7ab09aaded45..8c0ff5e7d56ac24f70f87d0f40e0a25ca5b9907e 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java @@ -71,19 +71,18 @@ public final class PluginFinder { * The url for the jar. * @return A list containing all available repository-classes or null, if an exception occurred. */ - @SuppressWarnings("unchecked") - public List<Class<AbstractRepository>> getAllRepositoriesWithinJar(final URL url) { + public List<Class<?>> getAllRepositoriesWithinJar(final URL url) { // Get a list containing all available classes within the given jar final List<Class<?>> clazzes = this.getAllClassesWithinJar(url); - List<Class<AbstractRepository>> result = null; + List<Class<?>> result = null; if (clazzes != null) { - result = new ArrayList<Class<AbstractRepository>>(); + result = new ArrayList<Class<?>>(); for (final Class<?> clazz : clazzes) { // This is the cast resulting in an unchecked cast warning. if (clazz.isAnnotationPresent(this.repositoryAnnotationClass) && this.abstractRepositoryClass.isAssignableFrom(clazz)) { - result.add((Class<AbstractRepository>) clazz); + result.add(clazz); } } } @@ -101,17 +100,16 @@ public final class PluginFinder { * The class loader used to load the classes. * @return A list containing all available plugin-classes or null, if an exception occurred. */ - @SuppressWarnings("unchecked") - public List<Class<AbstractPlugin>> getAllPluginsWithinJar(final URL url) { + public List<Class<?>> getAllPluginsWithinJar(final URL url) { final List<Class<?>> clazzes = this.getAllClassesWithinJar(url); - List<Class<AbstractPlugin>> result = null; + List<Class<?>> result = null; if (clazzes != null) { - result = new ArrayList<Class<AbstractPlugin>>(); + result = new ArrayList<Class<?>>(); for (final Class<?> clazz : clazzes) { // This is the cast resulting in an unchecked cast warning. if (clazz.isAnnotationPresent(this.pluginAnnotationClass) && this.abstractPluginClass.isAssignableFrom(clazz)) { - result.add((Class<AbstractPlugin>) clazz); + result.add(clazz); } } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java new file mode 100644 index 0000000000000000000000000000000000000000..fbcedbbf5722e5ca9057a4cff4556c316d8cae05 --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/exception/ProjectLoadException.java @@ -0,0 +1,25 @@ +package kieker.webgui.common.exception; + +public class ProjectLoadException extends Exception { + /** + * The serial version UID. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new instance of this class. + */ + public ProjectLoadException() { + // No code necessary + } + + /** + * Creates a new instance of this class using the given parameters. + * + * @param msg + * The message used for the exception. + */ + public ProjectLoadException(final String msg) { + super(msg); + } +} diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml index 446a671d4126a3e7bf1635fc324eb9c32e6ac79e..aa3891395ac77da6dbe1e89b12fb6fa1fe2a421b 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml @@ -25,27 +25,23 @@ <script language="javascript" type="text/javascript" src="../js/jit.js"></script> <script language="javascript" type="text/javascript" src="../js/flowEditor.js"></script> + <script type="text/javascript" src="https://getfirebug.com/firebug-lite.js"></script> + <script> nodeClickListener = function(node, info, e) { - nodeClickCommand([{name : 'ID', value : node.id}, {name : 'type', value : node.data.$nodeType}]); - // var graphNode = graph.getNode(node.id); - // graphNode.name = 'Hallo, Welt'; - // graph.refresh(); + nodeClickCommand([{name : 'ID', value : node.id}]); } - nodeRemoveListener = function(node, info, e) { - nodeRemoveCommand([{name : 'ID', value : node.id}, {name : 'type', value : node.data.$nodeType}]); + nodeRemoveListener = function(node) { + nodeRemoveCommand([{name : 'ID', value : node.id}]); } - edgeCreateListener = function(sourceNodeID, targetNodeID, sourcePortID, targetPortID) { - + edgeCreateListener = function(sourceNode, targetNode, sourcePort, targetPort) { + edgeCreateCommand([{name : 'sourcePortID', value : sourcePort.id},{name : 'targetPortID', value : targetPort.id}]); } - edgeRemoveListener = function(sourceNodeID, targetNodeID, sourcePortID, targetPortID) { - - } - - nodeMouseListener = function(node, info, e) { + edgeRemoveListener = function(sourceNode, targetNode, sourcePort, targetPort) { + edgeRemoveCommand([{name : 'sourcePortID', value : sourcePort.id},{name : 'targetPortID', value : targetPort.id}]); } </script> </h:head> @@ -57,6 +53,8 @@ <h:form id="hiddenNodeProperties" style="display:none"> <p:remoteCommand name="nodeClickCommand" action="#{currentAnalysisEditorGraphBean.nodeClicked()}" update=":propertiesForm"/> <p:remoteCommand name="nodeRemoveCommand" action="#{currentAnalysisEditorGraphBean.nodeRemoved()}" update=":propertiesForm"/> + <p:remoteCommand name="edgeCreateCommand" action="#{currentAnalysisEditorGraphBean.edgeCreated()}"/> + <p:remoteCommand name="edgeRemoveCommand" action="#{currentAnalysisEditorGraphBean.edgeRemoved()}"/> </h:form> <p:layout fullPage="true"> @@ -134,7 +132,7 @@ <h:outputText value="#{currentAnalysisEditorBean.selectedPlugin.classname}" rendered="#{rowIndex == 0}"/> <p:inplace editor="true" rendered="#{rowIndex == 1}" > <p:inputText value="#{currentAnalysisEditorBean.selectedPlugin.name}" /> - <p:ajax event="save" /> + <p:ajax event="save" listener="#{currentAnalysisEditorGraphBean.renameNode(currentAnalysisEditorBean.selectedPlugin, currentAnalysisEditorBean.selectedPlugin.name)}" /> </p:inplace> <p:inplace editor="true" rendered="#{rowIndex > 1}"> <p:inputText value="#{property.value}" /> diff --git a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js index de8e6511bdd91e0f42912ad6cbddb06492983bab..2a4d90e24ca209347b13b62142fb837c7f8dff8f 100644 --- a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js +++ b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js @@ -46,17 +46,19 @@ function GraphFlow(){ /** Color Palettes */ var nodeFillColor = []; - nodeFillColor['Filter'] = "#DEDEDE"; - nodeFillColor['Repository'] = "#EFAC6A"; - nodeFillColor['Reader'] = "#6AEF73"; + //nodeFillColor['Filter'] = "#DEDEDE"; + //nodeFillColor['Repository'] = "#EFAC6A"; + //nodeFillColor['Reader'] = "#6AEF73"; + nodeFillColor['nodeFamily'] = "#DEDEDE"; var nodeStrokeColor = []; - nodeStrokeColor['Filter'] = "#4D4D4D"; - nodeStrokeColor['Repository'] = "#735332"; - nodeStrokeColor['Reader'] = "#327337"; + //nodeStrokeColor['Filter'] = "#4D4D4D"; + //nodeStrokeColor['Repository'] = "#735332"; + //nodeStrokeColor['Reader'] = "#327337"; nodeStrokeColor['inputPort'] = "#AA0000"; nodeStrokeColor['outputPort'] = "#AA0000"; nodeStrokeColor['repositoryPort'] = "#AA0000"; + nodeStrokeColor['nodeFamily'] = "#4D4D4D"; var nodeColorFocus = "#0098BE", edgeColor = "#114270", @@ -115,6 +117,10 @@ function GraphFlow(){ ///////////////////////////////////////////// + this.scaleGraphToFit = function(){ + //fd.canvas.scaleOffsetX = 10; + } + /** Sets the mouseCursor to a new one. If the new cursor is null, it will be reset to the default mouse cursor. @@ -127,6 +133,9 @@ function GraphFlow(){ } } + /** + Converts the Graph to dot language, returning it as a string. + */ this.graphToDot = function(filename){ var node; var dotGraph = 'digraph structs {\nnode [shape=plaintext];\n\n' @@ -290,10 +299,10 @@ function GraphFlow(){ onDragCancel, onDragEnd Or one of these events: - onCreateEdge(sourceNodeID, targetNodeID, sourcePortID, targetPortID), - onCreateNode(nodeID), - onRemoveEdge(sourceNodeID, targetNodeID, sourcePortID, targetPortID), - onRemoveNode(nodeID) + onCreateEdge(sourceNode, targetNode, sourcePort, targetPort) : boolean, + onCreateNode(node) : boolean, + onRemoveEdge(sourceNode, targetNode, sourcePort, targetPort) : boolean, + onRemoveNode(node) : boolean When altering the graph, do not forget to refresh() it at the end! @param event - the name of the mouse event ( see list above) @@ -306,6 +315,46 @@ function GraphFlow(){ listener[event].push(listenerFunction); } + /** + Calls all listener that are registered under the eventName. + @param eventName - see 'addListener()' + @param arguments - an array of arguments. For mouseEvents it is [node, eventInfo, e], + for nodeEvents it is [nodeID], + and for edgeEvents it is [sourceNodeID, targetNodeID, sourcePortID, targetPortID] + @return true if an editor action is permitted + */ + function callListener(eventName, arguments){ + //Log.write("call "+eventName+" "+arguments.length + " ("+Math.random()+")"); + var lArr = listener[eventName]; + var permitted = true; + var testval; + + if(lArr == undefined){ + return true; + } + // iterate through all listener this event concerns + if(arguments.length == 3){ + for(var l=0; l < lArr.length; l++){ + lArr[l](arguments[0], arguments[1], arguments[2]); + } + }else if(arguments.length == 1){ + for(var l=0; l < lArr.length; l++){ + testval = lArr[l](arguments[0]); + if(testval == false){ + permitted = false; + } + } + }else if(arguments.length == 4){ + for(var l=0; l < lArr.length; l++){ + testval = lArr[l](arguments[0], arguments[1], arguments[2], arguments[3]); + if(testval == false){ + permitted = false; + } + } + } + return permitted; + } + /** Adds a listener that forbids edge creation if... ... an edge with the same source and target exists. @@ -316,50 +365,81 @@ function GraphFlow(){ */ this.addEdgeConstraints = function(){ addListener("onCreateEdge", - function(sourceFamilyID, targetFamilyID, sourcePortID, targetPortID){ - + function(sourceFamily, targetFamily, sourcePort, targetPort){ // remove Edge if it leads to itself - if( sourcePortID == targetPortID){ - removeEdge(sourcePortID, targetPortID); - return; + if( sourcePort.id == targetPort.id){ + return false; } - var sourcePort = getNode(sourcePortID), - targetPort = getNode(targetPortID); - // remove Edge if the connected nodes share the same type if(sourcePort.data.$type == targetPort.data.$type){ - removeEdge(sourcePortID, targetPortID); - return; + return false; } - var target = getNode(targetFamilyID), - goesToRepo = (target.data.$nodeType == "Repository"), + var goesToRepo = (targetFamily.data.$nodeType == "Repository"), isRepoPort = (sourcePort.data.$type == "repositoryPort"); // remove Edge if it is falsely connected to repository ports if(goesToRepo != isRepoPort){ - removeEdge(sourcePortID, targetPortID); - return; + return false; } // remove duplicate Edge var adja = sourcePort.adjacencies, duplicate = false; for(var a=0; a < adja.length; a++){ - if(adja[a].nodeTo == targetPortID){ + if(adja[a].nodeTo == targetPort.id){ if(duplicate){ - removeEdge(sourcePortID, targetPortID); - return; + return false; } else{ duplicate = true; } } } + return true; }); } + /** + Iterates through all nodes and ports, calling a given function for each. + @param nodeFunction - a single parameter function, called on a node. + */ + this.iterateAllNodes = function(nodeFunction){ + var node; + for(var n=1; n < json.length; n++){ + node = json[n]; + nodeFunction(node); + } + } + + /** + Changes the colors of the entire graph. + */ + this.setNodeStyle = function(fillColor, strokeColor, portColor){ + nodeFillColor['nodeFamily'] = fillColor; + nodeStrokeColor['nodeFamily'] = strokeColor; + nodeStrokeColor['inputPort'] = portColor; + nodeStrokeColor['outputPort'] = portColor; + nodeStrokeColor['repositoryPort'] = portColor; + + var node, type; + for(var n = 1; n < json.length; n++){ + node = json[n]; + type = node.data.$type; + + node.data.$color = nodeStrokeColor[type]; + + if(type == "nodeFamily" ||type == "crossBox"){ + node.data.$fillColor = nodeFillColor["nodeFamily"]; + node.data.$color = nodeStrokeColor["nodeFamily"]; + }else{ + node.data.$color = nodeStrokeColor[type]; + } + } + refresh(); + } + /** Changes all colors of the graph. Colors that should not be changed are given null as argument. */ @@ -385,35 +465,6 @@ function GraphFlow(){ }*/ - /** - Calls all listener that are registered under the eventName. - @param eventName - see 'addListener()' - @param arguments - an array of arguments. For mouseEvents it is [node, eventInfo, e], - for nodeEvents it is [nodeID], - and for edgeEvents it is [sourceNodeID, targetNodeID, sourcePortID, targetPortID] - */ - function callListener(eventName, arguments){ - //Log.write("call "+eventName+" "+arguments.length + " ("+Math.random()+")"); - var lArr = listener[eventName]; - if(lArr == undefined){ - return; - } - // iterate through all listener this event concerns - if(arguments.length == 3){ - for(var l=0; l < lArr.length; l++){ - lArr[l](arguments[0], arguments[1], arguments[2]); - } - }else if(arguments.length == 1){ - for(var l=0; l < lArr.length; l++){ - lArr[l](arguments[0]); - } - }else if(arguments.length == 4){ - for(var l=0; l < lArr.length; l++){ - lArr[l](arguments[0], arguments[1], arguments[2], arguments[3]); - } - } - } - /** Moves all nodes to their respective positions, which are stored within their data. We need this function to dynamically remove graph elements @@ -500,8 +551,8 @@ function GraphFlow(){ // add big node box // change node color, depending on its type - var nodeColor = nodeFillColor[nodeType], - strokeColor = nodeStrokeColor[nodeType]; + var nodeColor = nodeFillColor["nodeFamily"], + strokeColor = nodeStrokeColor["nodeFamily"]; var newNode = { "data": { "$dim": size, @@ -535,7 +586,7 @@ function GraphFlow(){ "$color": strokeColor, "$fillColor": nodeColor, }, - "id": nodeFamily.id+"_close", + "id": nodeFamily.id+".close", "name": "x" }; json.push(closeButton); @@ -596,8 +647,9 @@ function GraphFlow(){ "$xPos": x, "$yPos": y, "$color": nodeStrokeColor[loopType], + "$fillColor": nodeColor, "$tooltip": port.tooltip}, - "id": nodeFamily.id+"_"+port.id, + "id": nodeFamily.id+"."+port.id, "name": port.name}; if(p == 0){ @@ -611,7 +663,7 @@ function GraphFlow(){ } // call listener - callListener("onCreateNode", [nodeFamily.id]); + callListener("onCreateNode", [newNode]); } /** @@ -619,10 +671,16 @@ function GraphFlow(){ @param nodeFamily - the node which is to be removed */ this.removeNode = function(nodeFamily){ + // call listener + var deletionValid = callListener("onRemoveNode", [nodeFamily]); + + if(!deletionValid){ + return; + } var deleteFrom = nodeFamily.data.$jsonIndex, - deleteUntil = deleteFrom+ nodeFamily.data.$portCount+1, - familyID = nodeFamily.id; + deleteUntil = deleteFrom+ nodeFamily.data.$portCount+1; + // delete nodeFamily and closeButton delete json[deleteFrom]; @@ -656,9 +714,6 @@ function GraphFlow(){ } } - // call listener - callListener("onRemoveNode", [familyID]); - return; } @@ -711,24 +766,38 @@ function GraphFlow(){ * @param targetID the id of the node where the edge ends * @return true if the edge was successfully added */ - this.addEdge = function(sourceID, targetID){ + this.addEdge = function(sourceID, targetID, edgeLabel){ // look up the source node and the nodeFamilys of both nodes - var source, sourceFamilyID, targetFamilyID, lastFamilyID; - for(var n=0; n< json.length; n++){ + var source, sourceFamily, targetFamily, lastFamily, target; + for(var n = 0; n< json.length; n++){ var loopNode = json[n]; if(loopNode.data.$type == "nodeFamily"){ - lastFamilyID = loopNode.id + lastFamily = loopNode; } else if(loopNode.id == sourceID){ source = loopNode; - sourceFamilyID = lastFamilyID; + sourceFamily = lastFamily; } else if(loopNode.id == targetID){ - targetFamilyID = lastFamilyID; + target = loopNode; + targetFamily = lastFamily; + } + } + + // call listener if the edge is not connected to the dummy node and check for permission + if(sourceID != mouseNode.id && targetID != mouseNode.id){ + if (!callListener("onCreateEdge", [sourceFamily, targetFamily, source, target])){ + return false; } } + // if edge is connected to mouse, there is no targetFamily... + var targetFamilyID = null; + if(targetFamily != undefined){ + targetFamilyID = targetFamily.id; + } + var adja = source.adjacencies; // source node does not exist? if(adja == undefined){ @@ -740,19 +809,19 @@ function GraphFlow(){ "nodeTo": targetID, "nodeFrom": sourceID, "data": {"$direction" : [ sourceID, targetID ], - "$targetFamily" : targetFamilyID} + "$targetFamily" : targetFamilyID}, }; + + if(edgeLabel != null && edgeLabel != undefined){ + edge.data.$label = edgeLabel; + } adja.push(edge); if(sourceID == mouseNode.id || targetID == mouseNode.id){ edge.data.$type = "mouseArrow"; - return true; } - // call listener (only if the edge is not connected to the dummy node!) - callListener("onCreateEdge", [sourceFamilyID, targetFamilyID, sourceID, targetID]); - return true; } @@ -762,30 +831,39 @@ function GraphFlow(){ @param targetID the id of the target Node */ this.removeEdge = function(sourceID, targetID){ - var source = getNode(sourceID), - adja = source.adjacencies; - - // nothing to delete? - if(adja == undefined){ - return; - } - + var source, target; + // look up nodeFamily IDs - var sourceFamilyID, targetFamilyID, lastFamilyID; + var sourceFamily, targetFamily, lastFamily; for(var n=0; n< json.length; n++){ var loopNode = json[n]; if(loopNode != undefined){ // <- this happens on Node Deletion if(loopNode.data.$type == "nodeFamily"){ - lastFamilyID = loopNode.id + lastFamily = loopNode; } else if(loopNode.id == sourceID){ - sourceFamilyID = lastFamilyID; + sourceFamily = lastFamily; + source = loopNode; } else if(loopNode.id == targetID){ - targetFamilyID = lastFamilyID; + targetFamily = lastFamily; + target = loopNode; } } } + + // call listener and ask for permission to delete + if(sourceID != mouseNode.id && targetID != mouseNode.id){ + if(!callListener("onRemoveEdge", [sourceFamily, targetFamily, source, target])){ + return; + } + } + + // nothing to delete? + if(source == undefined){ + return; + } + var adja = source.adjacencies; for(var a=0; a< adja.length; a++){ if(adja[a].nodeTo == targetID){ @@ -795,10 +873,6 @@ function GraphFlow(){ } } - // call listener - if(sourceID != mouseNode.id && targetID != mouseNode.id){ - callListener("onRemoveEdge", [sourceFamilyID, targetFamilyID, sourceID, targetID]); - } } /** @@ -857,7 +931,6 @@ function GraphFlow(){ // animation parameters var trans = $jit.Trans.Quint.easeOut, dur= 200; - // clean up the old highlight if(hover != null){ @@ -867,11 +940,9 @@ function GraphFlow(){ } else{ var type = hover.data.$type; - if(type == "nodeFamily"){ - hover.setData('color', nodeStrokeColor[hover.data.$nodeType], 'end'); - } - else if(type == "crossBox"){ - hover.setData('color', nodeStrokeColor[hover.data.$family.data.$nodeType], 'end'); + + if(type == "crossBox"){ + hover.setData('color', nodeStrokeColor["nodeFamily"], 'end'); } else{ hover.setData('color', nodeStrokeColor[type], 'end'); @@ -1137,10 +1208,12 @@ function GraphFlow(){ // add Edge if the selectedNode differs from the clickedNode var newEdgeAdded; if(selectedNode.from == "inputPort"){ - newEdgeAdded = addEdge(node.id, selectedNode.id); + var label = mouseNode.adjacencies[0].data.$label; + newEdgeAdded = addEdge(node.id, selectedNode.id, label); } else{ - newEdgeAdded = addEdge(selectedNode.id, node.id); + var label = selectedNode.label; + newEdgeAdded = addEdge(selectedNode.id, node.id, label); } if(newEdgeAdded){ refresh(); @@ -1153,13 +1226,12 @@ function GraphFlow(){ else if(mouseOverEdge){ // no selection yet if(selectedNode == null){ - selectedNode = {"id":node.data.$direction[0], "from":"outputPort"};//nodeTo - + selectedNode = {"id":node.data.$direction[0], "from":"outputPort", "label":node.data.$label};//nodeTo // remove selectedEdge + var label = node.data.$label; removeEdge(selectedNode.id, node.data.$direction[1]); - // add edge to mouseNode - addEdge(selectedNode.id, mouseNode.id); + addEdge(selectedNode.id, mouseNode.id, label); refresh(); return; } @@ -1257,29 +1329,33 @@ function GraphFlow(){ // test data function init(){ - var addNodeCounter = 0; var graph = GraphFlow(); - // add a new listener: - graph.addListener("onRightClick", function(node,info,e){ - var newNode = {"id":"nuNode"+addNodeCounter, - "name":"node #"+addNodeCounter, - "nodeClass":"someClassAgain", - "tooltip":"i am new and shiny!"}; - graph.addFilter(info.getPos().x, info.getPos().y, newNode, null, - [{"name":"inputPort","id":"ip1"}], - [{"name":"outputPort", "id":"op1"}]); - graph.refresh(); - addNodeCounter++; - }); - /*graph.addListener("onRightClick", function(node, info, e){ - graph.graphToDot(null); - });*/ + + /** These listeners add a "new"-flag to a freshly added node. New nodes cannot be deleted until saved with a right click **/ + + graph.addListener("onCreateNode", function(node){ + node.data.$saved = false; + }); + graph.addListener("onRightClick", function(node, info, e){ + graph.iterateAllNodes( + function(node){ + if(node.data.$type == "nodeFamily"){ + node.data.$saved = true; + } + }); + graph.refresh(); + }); + graph.addListener("onRemoveNode", function(node){ + if(node.data.$saved == false){ + alert("This node has not been saved!\n \ Right click node to save!"); + return false; + } + return true; + }); // adds a listener that removes invalid Edges graph.addEdgeConstraints(); - //graph.addListener("onRemoveNode", function(nodeID){alert("bye bye, "+ nodeID+".");}); - // define graph by adding nodes var node1 = {"id":"superNode1", "name":"i am node", @@ -1298,14 +1374,14 @@ function init(){ {"name":"inputPort","id":"ip6"}], [{"name":"outputPort", "id":"op1"}]); - + // create graph graph.initGraph(null); // add nodes after graph creation var node2 = {"id":"superNode2", "name":"Super Repo", - "nodeClass":"Reposiotry", + "nodeClass":"Repository", "tooltip":"look at me, i'm another node!"}; graph.addRepository(100, 0, node2,{"name":"inputPort", "id":"ip1"}); var node3 = {"id":"superNode3", @@ -1313,6 +1389,8 @@ function init(){ "nodeClass":"Reader", "tooltip":"look at me, i'm another node!"}; graph.addReader(0, 100, node3,[{"name":"repoPort", "id":"rp1"}], [{"name":"outputPort", "id":"op1"}]); + + graph.addEdge("superNode1.rp1", "superNode2.ip1", "I'm a label!"); // refresh graph to show the new nodes graph.refresh(); } diff --git a/Kieker.WebGUI/src/main/webapp/js/jit.js b/Kieker.WebGUI/src/main/webapp/js/jit.js index 02ffca3052eb09a17676669aedf4c988896c0139..09d7cb39abcae2b5cf61a8c4b94e1939029b79c0 100644 --- a/Kieker.WebGUI/src/main/webapp/js/jit.js +++ b/Kieker.WebGUI/src/main/webapp/js/jit.js @@ -17808,7 +17808,7 @@ $jit.FlowGraph.$extend = true; xmh = bx - h; // draw R - ctx.fillStyle = "#FFFFFF"; + ctx.fillStyle = node.getData('fillColor'); ctx.beginPath(); ctx.moveTo(bx - j , by + h); ctx.lineTo(xh , by + h); @@ -17854,9 +17854,20 @@ $jit.FlowGraph.$extend = true; by = pos.y+size/4, ctx = canvas.getCtx(); - // draw close-button area + // draw rectangle this.nodeHelper.rectangle.render('fill', {x: bx, y: by}, size, size/2, canvas); + // draw arrow + var octo = size/8, + xp = bx - size/2 + octo; + ctx.fillStyle = node.getData('fillColor'); + ctx.beginPath(); + ctx.moveTo(xp, by - octo); + ctx.lineTo(bx , by); + ctx.lineTo(xp, by + octo); + ctx.closePath(); + ctx.fill(); + }, 'contains': function(node, pos){ @@ -17876,9 +17887,20 @@ $jit.FlowGraph.$extend = true; by = pos.y+size/4, ctx = canvas.getCtx(); - // draw close-button area + // draw rectangle this.nodeHelper.rectangle.render('fill', {x: bx, y: by}, size, size/2, canvas); + // draw arrow + var octo = size/8; + //xp = bx + size/2 - octo; + ctx.fillStyle = node.getData('fillColor'); + ctx.beginPath(); + ctx.moveTo(bx, by - octo); + ctx.lineTo(bx + size/2 - octo , by); + ctx.lineTo(bx, by + octo); + ctx.closePath(); + ctx.fill(); + }, 'contains': function(node, pos){ @@ -17917,7 +17939,7 @@ $jit.FlowGraph.$extend = true; */ FlowGraph.Plot.EdgeTypes = new Class({ 'none': $.empty, - + 'flowArrow': { 'render': function(adj, canvas) { @@ -17938,8 +17960,21 @@ $jit.FlowGraph.$extend = true; to.x += dim; from.x -= dim; } - + this.edgeHelper.flowarrow.render(from, to, dim, inv, canvas); + + // add the label + var label = adj.data.$label; + if(label != undefined){ + var ctx = canvas.getCtx(); + ctx.font = (1.23 * dim)+"px Arial"; + + var midX = (from.x + to.x - ctx.measureText(label).width) / 2, + midY = (from.y + to.y -dim) / 2; + + ctx.fillText(label, midX, midY); + } + }, 'contains': function(adj, pos) { var dim = adj.getData('dim'), @@ -17966,7 +18001,6 @@ $jit.FlowGraph.$extend = true; 'mouseArrow': { 'render': function(adj, canvas) { - var dim = adj.getData('dim'), direction = adj.data.$direction, inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id), @@ -17984,6 +18018,18 @@ $jit.FlowGraph.$extend = true; } this.edgeHelper.flowarrow.render(from, to, dim, inv, canvas); + + // add the label + // apparently this throws an error on FireFox... + /*var label = adj.data.$label; + if(label != undefined){ + var ctx = canvas.getCtx(); + ctx.font = (1.23 * dim)+"px Arial"; + + var midX = (from.x + to.x - ctx.measureText(label).width) / 2, + midY = (from.y + to.y -dim) / 2; + ctx.fillText(label, midX, midY); + }*/ }, 'contains': function(adj, pos) { return false;