aboutsummaryrefslogtreecommitdiffstats
path: root/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js
diff options
context:
space:
mode:
authorhuangjian <huang.jian12@zte.com.cn>2016-08-31 16:47:33 +0800
committerhuangjian <huang.jian12@zte.com.cn>2016-08-31 16:47:33 +0800
commitfa49e78cc199526a9e33b59c5194f8e3bf0f0952 (patch)
tree3478e867a8f304266dbceca6e992cceca410ede4 /winery/org.eclipse.winery.topologymodeler/src/main/webapp/js
parent159d40f0011559c8f82338b29dca1bffd700f2c8 (diff)
Add winery source code
Change-Id: I1c5088121d79b71098c3cba1996c6f784737532e Issue-id: TOSCA-49 Signed-off-by: huangjian <huang.jian12@zte.com.cn>
Diffstat (limited to 'winery/org.eclipse.winery.topologymodeler/src/main/webapp/js')
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js82
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js13
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js13
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js13
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js13
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js143
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js265
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js634
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js330
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js178
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js234
-rw-r--r--winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js308
12 files changed, 2226 insertions, 0 deletions
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js
new file mode 100644
index 0000000..7b7f20b
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+ // also loaded from the repository
+
+// TODO: winery-common should be required -> encodeID; typing could be required (but that is no AMD module)
+define([], function() {
+ $("#artifactTemplateName").typing({
+ start: function(event, $elem) {
+ flagArtifactTemplateNameAsUpdating();
+ },
+ stop: function(event, $elem) {
+ checkArtifactTemplateName();
+ }
+ });
+
+ $("#artifactTemplateNS").on("blur", checkArtifactTemplateName).on("change", checkArtifactTemplateName).on("focus", flagArtifactTemplateNameAsUpdating);
+
+ var repositoryURL;
+
+ return {
+ setRepositoryURL: function(url) {
+ repositoryURL = url;
+ },
+ checkArtifactTemplateName: checkArtifactTemplateName,
+ flagArtifactTemplateNameAsUpdating: flagArtifactTemplateNameAsUpdating
+ };
+
+ function checkArtifactTemplateName() {
+ var ns = $("#artifactTemplateNS").val();
+ var name = $("#artifactTemplateName").val();
+ var url = repositoryURL + "/artifacttemplates/" + encodeID(ns) + "/" + encodeID(name) + "/";
+ if (name == "") {
+ var valid = false;
+ var invalidReason = "No name provided";
+ setValidityStatus(valid, invalidReason);
+ } else {
+ $.ajax(url, {
+ type: 'HEAD',
+ dataType: 'html',
+ error: function(jqXHR, textStatus, errorThrown) {
+ if (jqXHR.status == 404) {
+ // artifact template does not exist: everything is allright
+ setValidityStatus(true, null);
+ } else {
+ setValidityStatus(false, textStatus);
+ }
+ },
+ success: function(data, textStatus, jqXHR) {
+ setValidityStatus(false, "artifact template already exists");
+ }
+ });
+ }
+ }
+
+ function flagArtifactTemplateNameAsUpdating() {
+ $("#artifactTemplateNameIsValid").removeClass("invalid").removeClass("valid").addClass("unknown");
+ $("#artifactTemplateNameIsInvalidReason").text("");
+ }
+
+ function setValidityStatus(valid, invalidReason) {
+ $("#artifactTemplateNameIsValid").removeClass("unknown");
+ if (valid) {
+ $("#artifactTemplateNameIsValid").addClass("valid");
+ $("#artifactTemplateNameIsInvalidReason").text("Ok");
+ } else {
+ $("#artifactTemplateNameIsValid").addClass("invalid");
+ $("#artifactTemplateNameIsInvalidReason").text(invalidReason);
+ }
+ }
+
+
+}); \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js
new file mode 100644
index 0000000..20e2cde
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js
@@ -0,0 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js
new file mode 100644
index 0000000..20e2cde
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js
@@ -0,0 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js
new file mode 100644
index 0000000..20e2cde
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js
@@ -0,0 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js
new file mode 100644
index 0000000..20e2cde
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js
@@ -0,0 +1,13 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js
new file mode 100644
index 0000000..fa37897
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+/**
+ * This file contains supporting functions for the rendering a topology template
+ */
+define(
+ ["jsplumb", "winery-support-common"],
+ function (globdefa, wsc) {
+ var readOnly = false;
+
+ var module = {
+ initNodeTemplate: initNodeTemplate,
+ handleConnectionCreated: handleConnectionCreated,
+ imageError: imageError,
+ setReadOnly: setReadOnly
+ };
+
+ return module;
+
+ /**
+ * @param nodeTemplateShape the set of node template shapes to initialize
+ * @param makeDraggable true if the nodeTemplates should be made draggable
+ */
+ function initNodeTemplate(nodeTemplateShapeSet, makeDraggable) {
+ if (makeDraggable) {
+ jsPlumb.draggable(nodeTemplateShapeSet);
+ }
+ jsPlumb.makeTarget(nodeTemplateShapeSet, {
+ anchor:"Continuous",
+ endpoint:"Blank"
+ });
+
+ // this function is defined in index.jsp via jsp functions
+ // as it depends on the available relationship types
+ createConnectorEndpoints(nodeTemplateShapeSet);
+
+ nodeTemplateShapeSet.addClass("layoutableComponent");
+
+ nodeTemplateShapeSet.each(function(idx, s) {
+ var shape = $(s);
+
+ var id = shape.attr("id");
+
+ // KV Properties
+ var props = shape.children(".propertiesContainer")
+ .children(".content")
+ .children("table")
+ .children("tbody");
+ if (!readOnly) {
+ props.find(".KVPropertyValue").editable();
+ }
+
+ // Deployment Artifacts
+ var fu = shape.children(".deploymentArtifactsContainer")
+ .children(".content")
+ .children(".addnewartifacttemplate")
+ .children(".fileupload");
+ fu.attr("data-url", "nodetemplates/" + wsc.encodeId(id) + "/deploymentartifacts/");
+ });
+
+ // nodeTemplateShapeSet.children(".deploymentArtifactsContainer").children(".content").children(".deploymentArtifact").each(function(index, e) {
+ // addnewfileoverlay could be added here
+ // $(this).
+ // });
+ }
+
+ /**
+ * Handles the creation of connections by jsPlumb
+ *
+ * Also called if connection is created during loading
+ */
+ function handleConnectionCreated(data) {
+ // might be called directly from here or by the event
+ // if called by jsPlumb infrastructure, we have to get rid of the surrounding element
+ var conn;
+ if (data.connection) {
+ conn = data.connection;
+ } else {
+ conn = data;
+ }
+ winery.debugConnData = conn;
+
+ var id = conn.id;
+ winery.connections[id] = {
+ // we store the id to have a default for the id
+ id: id,
+ // and use it also as starting point of a name
+ name: id,
+ // we do NOT copy the plain type here
+ // type is stored in the connection
+ // type: .getType()[0]
+ // BUT: we copy the detailed ns and id
+ nsAndLocalName: wsc.getNamespaceAndLocalNameFromQName(conn.getType()[0])
+ };
+ putToolTipInfo(conn);
+
+ // we have to manually show and hide the tooltips as Bootstrap's tooltip plugin does not work after a connection was highlighted.
+ conn.bind("mouseenter", function(conn,e) {
+ putToolTipInfo(conn);
+ });
+ conn.bind("mouseexit", function(conn,e) {
+ $("div.tooltip").remove();
+ // we have to replace the tooltip as
+ putToolTipInfo(conn);
+ });
+ }
+
+ function putToolTipInfo(conn) {
+ // add tooltip showing the relationship type
+ var svgElement = $(conn.canvas);
+ // the title attribute is shown in the tooltip
+ // set the relationship type as tooltip
+ // we show the localname only
+ var nsAndLocalName = winery.connections[conn.id].nsAndLocalName;
+ // Vino4TOSCA: type in brackets
+ var title = "(" + nsAndLocalName.localname + ")";
+ svgElement.tooltip({title: title});
+ }
+
+ /**
+ * Removes the image from the display. Used at images which could not be loaded
+ *
+ * Used via {@code <img onerror="imageError(this);" ... />}
+ */
+ function imageError(image) {
+ image.onError="";
+ image.style.visibility = "hidden";
+ }
+
+ function setReadOnly() {
+ readOnly = true;
+ }
+ }
+); \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js
new file mode 100644
index 0000000..434bec3
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+/**
+This .js file is shared between the Winery Repository and the Winery Topology Modeler
+
+This should be rewritten as AMD module. A start is made in winery-support-common.js.
+
+vConfirmYesNo is defined in jsp/shared/dialogs.jsp
+*/
+
+/**
+ * Highlights fields, which are required but not filled out by the user.
+ * The check for required fields is done via the CSS class "required"
+ *
+ * @return true if a required field is not filled with a value
+ */
+function highlightRequiredFields() {
+ var requiredFieldMissing = false;
+ // includes check input field attribute "required" of HTML5: http://www.w3.org/TR/html-markup/input.radio.html#input.radio.attrs.required
+ $("input.required:visible:enabled, input[required]:visible").each(function(){
+ if ($(this).val() == '') {
+ $(this).parent().addClass('has-warning');
+ requiredFieldMissing = true;
+ } else {
+ $(this).parent().removeClass('has-warning');
+ }
+ });
+ return requiredFieldMissing;
+}
+
+function vPnotify(text, title, type) {
+ require(["pnotify"], function() {
+ var notice = $.pnotify({
+ title: title,
+ text: text,
+ type: type
+ });
+ notice.on("click", function(e) {
+ var target = $(e.target);
+ if (target.is(".ui-pnotify-closer *, .ui-pnotify-sticker *, a")) {
+ // buttons clicked - call their functionality
+ return true;
+ } else {
+ // click on text leads to display of a dialog showing the complete content
+
+ var textDiv;
+ if (target.is("div.ui-pnotify-text")) {
+ textDiv = target;
+ } else {
+ textDiv = target.closest("div.ui-pnotify-container").find("div.ui-pnotify-text");
+ }
+
+ // put text into dialog and show it
+ $("#diagmessagetitle").text("Full notification");
+ $("#diagmessagemsg").html(textDiv.html());
+ $("#diagmessage").modal("show");
+
+ return false;
+ }
+ });
+ });
+}
+
+/**
+ * @param title optional title
+ */
+function vShowError(text, title) {
+ vPnotify(text, title, "error");
+}
+
+function vShowAJAXError(msg, jqXHR, errorThrown) {
+ vShowError(msg + "<br />" + errorThrown + "<br/>" + jqXHR.responseText);
+}
+
+/**
+ * @param title optional title
+ */
+function vShowNotification(text, title) {
+ vPnotify(text, title, "notification");
+}
+
+
+/**
+ * @param title optional title
+ */
+function vShowSuccess(text, title) {
+ vPnotify(text, title, "success");
+}
+
+/**
+ * Deletes the given resource with confirmation.
+ * if deletion fails, an error message is shown
+ *
+ * @param onSuccess: function(data, textStatus, jqXHR) to call if deletion has been successful
+ * @param onError: function(jqXHR, textStatus, errorThrown) called if deletion lead to an error (optional)
+ * @param onStart: function() called if user has agreed to delete resource (optional)
+ * @param withoutConfirmation if given, the resource is deleted without any confirmation
+ */
+function deleteResource(nameOfResource, url, onSuccess, onError, onStart, withoutConfirmation) {
+ var f = function() {
+ $.ajax({
+ url: url,
+ type: 'DELETE',
+ async: true,
+ error: function(jqXHR, textStatus, errorThrown) {
+ vShowAJAXError("Could not delete " + nameOfResource, jqXHR, errorThrown);
+ if (onError) onError();
+ },
+ success: function(data, textStatus, jqXHR) {
+ vShowSuccess("Successfully deleted " + nameOfResource);
+ onSuccess(data, textStatus, jqXHR);
+ }
+ });
+ };
+ if (withoutConfirmation) {
+ f();
+ } else {
+ vConfirmYesNo("Do you really want to delete " + nameOfResource + "?", f);
+ }
+}
+
+/**
+ * Function to create a td with two columns: one for a key and one for a value
+ *
+ * Has to be called from a td element: This function uses $(this) to determine the td
+ * It changes the content of the td to an input field. In case the input was updated, it is POSTed to the given URL
+ *
+ * TODO: The editing mode should use x-editable instead of a self-written input handling
+ *
+ * @param url the URL to post the key/value pair to
+ * @param keyName the keyname of the key - used at POST with <keyName>=<newKey>
+ * @param valueName the keyname of the value - used at POST with <valueName>=<newValue>
+ *
+ */
+function vCreateTdClickFunction(url, keyName, valueName) {
+ var inputId = "thingedit" + Math.floor((Math.random()*100)+1); ;
+ keyName = keyName || "key";
+ valueName = valueName || "value";
+
+ var f = function(e) {
+ var input = $("#" + inputId);
+ if (input.length != 0) {
+ // input field already there
+ return;
+ }
+
+ var td = $(this);
+ var oldPrefix = td.text();
+ var html = "<input id='" + inputId + "' value='" + oldPrefix + "'></input>";
+ td.html(html);
+
+ // new field generated, has to be looked up
+ input = $("#" + inputId);
+
+ input.keydown(function(e) {
+ if (e.keyCode == 27) {
+ // ESC key pressed
+ input.off("blur");
+ td.html(oldPrefix);
+ } else if (e.keyCode == 13) {
+ // enter key pressed
+ input.trigger("blur");
+ }
+ });
+
+ input.focus();
+
+ input.on("blur", function() {
+ var newPrefix = input.val();
+ if (newPrefix == oldPrefix) {
+ td.html(newPrefix);
+ } else {
+ var namespace = td.next().text();
+ newPrefixEscaped = escape(newPrefix);
+ namespaceEscaped = escape(namespace);
+ var data = keyName + "=" + newPrefixEscaped + "&" + valueName + "=" + namespaceEscaped;
+ $.ajax({
+ url: url,
+ type: "POST",
+ async: false,
+ data: data,
+ error: function(jqXHR, textStatus, errorThrown) {
+ vShowAJAXError("Could not update data", jqXHR, errorThrown);
+ input.focus();
+ },
+ success: function(data, textSTatus, jqXHR) {
+ vShowSuccess("Successfully updated data");
+ td.html(newPrefix);
+ }
+ });
+ }
+ });
+ };
+
+ return f;
+}
+
+/**
+ * This function is also availble at winery-support-common.js
+ * This function here is due to legacy reasons and all callers should move to winery-support-common.js
+ *
+ * @param qname a QName in the form {namespace}localname
+ * @return { namespace: namespace, localname: localname }
+ */
+function getNamespaceAndLocalNameFromQName(qname) {
+ var i = qname.indexOf("}");
+ var res = {
+ namespace : qname.substr(1,i-1),
+ localname : qname.substr(i+1)
+ };
+ return res;
+}
+
+/**
+ * Converts a QName of the form {namespace}id to the form prefix:id
+ * with a lookup of the prefix in the element
+ *
+ * returns wrong data if prefix wsa not found
+ */
+function getQNameOutOfFullQName(fullQname, element) {
+ var nsAndId = getNamespaceAndLocalNameFromQName(fullQname);
+ var prefix = element.lookupPrefix(nsAndId.namespace);
+ var qname = prefix + ":" + nsAndId.localname;
+ return qname;
+}
+
+/**
+ * Converts a QName of the form prefix:id to the form {namespace}id
+ * with a lookup of the prefix in the element
+ *
+ * Currently not used anywhere
+ */
+function getFullQNameOutOfQName(qname, element) {
+ var i = qname.indexOf(":");
+ var prefix = qname.substr(0,i-1);
+ var localname = qname.substr(i+1);
+ var namespace = element.lookupPrefix(prefix);
+ return "{" + namespace + "}" + localname;
+}
+
+function encodeID(id) {
+ // the URL sent to the server should be the encoded id
+ id = encodeURIComponent(id);
+ // therefore, we have to encode it twice
+ id = encodeURIComponent(id);
+ return id;
+}
+
+// URLs from QName are provided by winery-support-common.js
+function makeArtifactTemplateURL(repoURL, namespace, id) {
+ return repoURL + "/artifacttemplates/" + encodeID(namespace) + "/" + encodeID(id) + "/";
+}
+
+if (!window.winery) window.winery = {};
+window.winery.BOOTSTRAP_ANIMATION_DURATION = 400;
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js
new file mode 100644
index 0000000..fa63f56
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js
@@ -0,0 +1,634 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Kálmán Képes - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+/**
+ * Implements abstractLayouter#layout and uses the Sugiyama method to apply a
+ * layered layout for TOSCA Topology Template graphs. The Sugiyama method
+ * proposes four steps to apply the layout:<br>
+ * First: Remove the cycles temporarly of the given graph by finding edges that
+ * are members of a cycle and reverse them<br>
+ * Second: Layer the nodes by using DFS/BFS and reduce the "height"/"width" of
+ * the graph<br>
+ * Third: Reduce the crossings of edges, by applying a suitable heuristic <br>
+ * Fourth: Assign the coordinates for the nodes/edges
+ *
+ * @param {Array}
+ * nodeTemplates, an array with div-elements of class
+ * ".NodeTemplateShape"
+ */
+define(function() {
+
+ var module = {
+ layout: layout
+ };
+ return module;
+
+ function layout(nodeTemplates) {
+
+
+ // Step 1: "Remove" Cycles
+ var hasCycle = _hasCycle(nodeTemplates);
+ var complementEdgesOfSubgraph = null;
+ if (hasCycle){
+ var edgesOfAcyclicSubgraph = _computeAcyclicSubgraph(nodeTemplates);
+ complementEdgesOfSubgraph = _computeEdgeComplement(nodeTemplates,
+ edgesOfAcyclicSubgraph);
+ _reverseEdges(complementEdgesOfSubgraph);
+ }
+
+ // Step 2: Assign Layers
+ _assignLayers(nodeTemplates);
+
+ // Step 3: reduce crossing of edges
+ _reduceCrossing(nodeTemplates);
+
+ // Step 4: assign coordinates
+ _assignCoordinates(nodeTemplates);
+
+ if(hasCycle){
+ _reverseEdges(complementEdgesOfSubgraph);
+ }
+
+ jsPlumb.repaintEverything();
+ // if used twice, edges are drawn straight and not random
+ jsPlumb.repaintEverything();
+
+ // cleanup -> remote internal data
+ $(nodeTemplates).removeAttr("layer");
+ $(nodeTemplates).removeAttr("layerPos");
+
+ }
+
+
+ function callsOnLayout(nodeTemplates) {
+ // TODO start sugiyama layout method with special handling of calls-on
+ // relations
+ }
+
+ /**
+ * Returns true if the given graph contains a cycle
+ * @param nodeTemplates the vertices of the graph, where cycle detection should be performed. The vertices should be an array of NodeTemplates
+ * @returns {Boolean} true iff a cycle was detected in the graph, else false
+ */
+ function _hasCycle(nodeTemplates) {
+ for(var i = 0; i < nodeTemplates.length;i++){
+ nodeTemplates[i].cycleMark = "notStarted";
+ }
+
+ var hasCycle = false;
+ var sources = _returnSources(nodeTemplates);
+
+ for(var i = 0; i < sources.length; i++){
+ _cycleSearch(sources[i],hasCycle);
+ if(hasCycle){
+ // remove marks
+ $(nodeTemplates).removeAttr("cycleMark");
+ return hasCycle;
+ }
+ }
+
+ return hasCycle;
+ }
+
+ var tarjanCounter = 1;
+
+ /**
+ * Detects cycles which are reachable by the given NodeTemplate of a graph. Algorithm is based on DFS
+ * @param nodeTemplate the starting NodeTemplate for the DFS
+ * @param hasCycle boolean variable to propagate if a cycle was found, init value should be 'false'
+ */
+ function _cycleSearch(nodeTemplate,hasCycle){
+ if(nodeTemplate.cycleMark = "inWork"){
+ hasCycle =true;
+ } else if(nodeTemplate.cycleMark ="notStarted") {
+ nodeTemplate.cycleMark ="inWork";
+ var successors = _returnSuccessors(nodeTemplate);
+ for(var i = 0 ; i < successors.length; i++){
+ _cycleSearch(successors[i]);
+ }
+ nodeTemplate.cycleMark ="finished";
+ }
+
+ }
+
+ /**
+ * Calculates strongly connected components according to Tarjans algorithm
+ * @param nodeTemplate a NodeTemplate that is used to begin the algorithm on
+ * @param foundCycle boolean variable to propagate if a cycle was found, init value should be 'false'
+ */
+ function _findComponent(nodeTemplate, foundCycle){
+ if(nodeTemplate.cycleMark == "inWork"){
+ foundCycle = true;
+ } else if(nodeTemplate.cycleMark == "notStarted"){
+ nodeTemplate.cycleMark = "inWork";
+ nodeTemplate.dfsNum = tarjanCounter;
+ nodeTemplate.compNum = tarjanCounter;
+ tarjanCounter++;
+ var successors = _returnSuccessors(nodeTemplate);
+ for(var i = 0; i < successors.length;i++){
+ var successor = successors[i];
+ if(successor.cycleMark != "finished"){
+ _findComponent(successor,foundCycle);
+ if(successor.compNum < nodeTemplate.compNum){
+ nodeTemplate.compNum = successor.compNum;
+ }
+ }
+ }
+ nodeTemplate.cycleMark = "finished";
+ }
+ }
+
+ /**
+ * Assigns x/y-Coordinates to the given NodeTemplates. It is assumed that the
+ * NodeTemplates are already layered, e.g. nodeTemplate.layer,
+ * nodeTemplate.layerPos is set.
+ *
+ * @param nodeTemplates
+ * the nodetemplate to set it's x/y coordinates
+ */
+ function _assignCoordinates(nodeTemplates) {
+ // get the layers out of the graph and the number of layers
+ var highestLayerNumber = _returnHighestLayerNumber(nodeTemplates);
+ var layerMap = {};
+ for ( var i = 1; i <= highestLayerNumber; i++) {
+ layerMap[i.toString()] = _returnLayer(nodeTemplates, i);
+ }
+
+ // determine the layer with the greatest width, width = {max(|U|) : U is
+ // layer of V}
+ var widestLayerWidth = -1;
+ for (layer in layerMap) {
+ if (layer.length > widestLayerWidth) {
+ widestLayerWidth = layer.length;
+ }
+ }
+
+ // get maximum node width
+ var maxNodeWidth = _getMaxWidthOfNodeArray(nodeTemplates);
+
+ // get maximum node height
+ var maxNodeHeight = _getMaxHeightOfNodeArray(nodeTemplates);
+
+ var editorArea = $("#editorArea")[0];
+ var height = editorArea.clientHeight;
+ var width = editorArea.clientWidth;
+
+ // TODO fine tuning or change the algorithm to handle callsOn Relations
+ // independently, e.g. draw it in an own tree/list/graph
+ // distance in height between nodes
+ var prettyHeight = 75;
+ for ( var i = highestLayerNumber; i > 0; i--) {
+ var layer = _returnLayer(nodeTemplates, i);
+ // distance in width between nodes
+ var prettyWidth = 75;
+ for ( var nodeIndex = 0; nodeIndex < layer.length; nodeIndex++) {
+ var layerNumber = i;
+ var layerPos = layer[nodeIndex].layerPos;
+ layer[nodeIndex].style.left = ((layerPos) * maxNodeWidth + prettyWidth)
+ + "px";
+ layer[nodeIndex].style.top = ((highestLayerNumber - layerNumber)
+ * maxNodeHeight + prettyHeight)
+ + "px"
+ prettyWidth += 50;
+ }
+ prettyHeight += 100;
+ }
+ }
+
+ /**
+ * Returns the height of the nodeTemplate with the highest height
+ * @param nodeTemplates an array of NodeTemplates
+ * @returns {Number} the maximum height of the given NodeTemplates
+ */
+ function _getMaxHeightOfNodeArray(nodeTemplates) {
+ var maxHeight = -1;
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ // TODO clientHeight responds to the complete page, not only the
+ // nodetemplate
+ if (maxHeight < nodeTemplates[i].clientHeight) {
+ maxHeight = nodeTemplates[i].clientHeight;
+ }
+ }
+ return maxHeight;
+ }
+
+ /**
+ * Returns the width of the nodeTemplate with the highest width
+ * @param nodeTemplates an array of NodeTemplates
+ * @returns {Number} the maximum width of the given NodeTemplates
+ */
+ function _getMaxWidthOfNodeArray(nodeTemplates) {
+ var maxWidth = -1;
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ // TODO clientWidth responds to the complete page, not only the
+ // nodetemplate
+ if (maxWidth < nodeTemplates[i].clientWidth) {
+ maxWidth = nodeTemplates[i].clientWidth;
+ }
+ }
+ return maxWidth;
+ }
+
+ /**
+ * Reduces crossing between the already layered Graph, given as a Array of NodeTemplates
+ * @param nodeTemplates the vertices of the layered graph, to reduce crossing between the layers
+ */
+ function _reduceCrossing(nodeTemplates) {
+ // initialize arbitrary orderings inside layers
+ _initLayerOrder(nodeTemplates);
+ var maxlayerlevel = _returnHighestLayerNumber(nodeTemplates);
+ // variable to reduce crossings of layer-pairs one by one
+ var layerCount = 1;
+ // variable to check if some change was made
+ var changed = true;
+ while (layerCount < maxlayerlevel | changed) {
+ // the algorithm should run until no position changes are made
+ if (layerCount == maxlayerlevel) {
+ layerCount = 1;
+ }
+ changed = false;
+
+ var layer = _returnLayer(nodeTemplates, layerCount + 1);
+
+ // INFO: This piece of code reduces the corssing according to the
+ // barycenter heuristic
+ for ( var i = 0; i < layer.length; i++) {
+ // deg(u), where u = layer[i]
+ var deg_u = _returnIncomingEdges(layer[i]).length
+ + _returnOutgoingEdges(layer[i]).length;
+
+ // sum of layerpos(v), for ever v where (u,v) exists in the graph
+ var outgoingEdges = _returnOutgoingEdges(layer[i]);
+ var sum = 0.0;
+ for ( var j = 0; j < outgoingEdges.length; j++) {
+ var node_v = outgoingEdges[j].target;
+ sum = sum + node_v.layerPos;
+ }
+
+ // barycenter heuristic
+ var bary_u = 1 / deg_u * sum;
+ layer[i].barycenter = bary_u;
+ }
+
+ // sorting the nodetemplates in the layer accorindg to the barycenter
+ layer.sort(function(a, b) {
+ return a.barycenter - b.barycenter
+ });
+
+ // rearrange positions in layer
+ for ( var i = 0; i < layer.length; i++) {
+ if (layer[i].layerPos != i + 1) {
+ // checks whether some change of the position is made
+ changed = true;
+ }
+ layer[i].layerPos = i + 1;
+ }
+
+ // get ready for the next layer
+ layerCount++;
+ }
+ }
+
+ /**
+ * Returns an array of NodeTemplates which are in the same Layer (nodeTemplate.layer)
+ */
+ function _returnLayer(nodeTemplates, layerNumber) {
+ var layer = new Array();
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ if ("layer" in nodeTemplates[i]
+ && nodeTemplates[i].layer == layerNumber) {
+ layer.push(nodeTemplates[i]);
+ }
+ }
+ layer.sort(function(a, b) {
+ return a.layerPos - b.layerPos;
+ });
+ return layer;
+ }
+
+ /**
+ * Returns the number of the highest layer of the given layered graph
+ * @param nodeTemplates the vertices of the layered graph as an array of NodeTemplates
+ * @returns {Number} the highest layer number
+ */
+ function _returnHighestLayerNumber(nodeTemplates) {
+ var layerNumber = -1;
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ if (!"layer" in nodeTemplates[i]) {
+ // some node has no "layer" property defined <=> graph isn't layered
+ return -1;
+ } else {
+ if (nodeTemplates[i].layer > layerNumber) {
+ layerNumber = nodeTemplates[i].layer;
+ }
+ }
+ }
+ return layerNumber;
+ }
+
+ /**
+ * Initializes a layering order in each layer of the given graph
+ * @param nodeTemplates the vertices of the graph to layer, as an array of NodeTemplates
+ */
+ function _initLayerOrder(nodeTemplates) {
+ // "map" := layerNumber : ordernumber
+ var orderingCount = {};
+ // init with 1 : 1
+ orderingCount["1"] = 1;
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ // get layer number of the nodetemplate
+ var layerNumber = nodeTemplates[i].layer;
+ // is the layer already in the map ?
+ if (layerNumber.toString() in orderingCount) {
+ // if yes, set the pos inside layer and increment for next node
+ nodeTemplates[i].layerPos = orderingCount[layerNumber.toString()];
+ orderingCount[layerNumber.toString()] = orderingCount[layerNumber
+ .toString()] + 1;
+ } else {
+ // if not, set the pos to 1 and add layer to map with count 2
+ nodeTemplates[i].layerPos = 1;
+ // init pos for next node
+ orderingCount[layerNumber.toString()] = 2;
+ }
+ }
+ }
+
+ /**
+ * Assigns layer numbers to the given graph
+ * @param nodeTemplates the vertices of the graph to assign layers to, as an array of NodeTemplates
+ */
+ function _assignLayers(nodeTemplates) {
+ var sinks = {};
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ if (_returnOutgoingEdges(nodeTemplates[i]).length == 0) {
+ sinks[i] = nodeTemplates[i];
+ }
+ }
+
+ // array of DIV delements
+ var nodesToCompute = new Array();
+
+ for ( var sink in sinks) {
+ // set layer level for the sinks and add the nodes which are reachable
+ // from there
+ sinks[sink].layer = 1;
+ var inEdges = _returnIncomingEdges(sinks[sink]);
+ for ( var i = 0; i < inEdges.length; i++) {
+ nodesToCompute.push(inEdges[i].source);
+ }
+ }
+
+ // set other layer levels
+ while (nodesToCompute.length != 0) {
+ var node = nodesToCompute.shift();
+ var layerNumber = _returnLayerNumber(node);
+ if (layerNumber == -1) {
+ nodesToCompute.push(node);
+ } else {
+ node.layer = layerNumber;
+ // add ingoing nodes to compute
+ var edgesToNodes = _returnIncomingEdges(node);
+ for ( var i = 0; i < edgesToNodes.length; i++) {
+ nodesToCompute.push(edgesToNodes[i].source);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the number of the layer a NodeTemplate belongs to
+ * @param nodeTemplate a NodeTemplate with already set layer position
+ * @returns {Number} the number of the layer the NodeTemplate belongs to
+ */
+ function _returnLayerNumber(nodeTemplate) {
+ var layerNumber = -1;
+ var outgoingEdges = _returnOutgoingEdges(nodeTemplate);
+ for ( var i = 0; i < outgoingEdges.length; i++) {
+ // we will see if it works here
+ var successorNode = outgoingEdges[i].target;
+ if ("layer" in successorNode) {
+ if (successorNode.layer + 1 >= layerNumber) {
+ layerNumber = successorNode.layer + 1;
+ }
+ } else {
+ layerNumber = -1;
+ break;
+ }
+ }
+ return layerNumber;
+ }
+
+ /**
+ * Returns all jsPlumb#Connection edges of the graph represented by the
+ * nodeTemplates array of NodeTemplateShape which aren't referenced in the
+ * subgraphEdges array of type jsPlumb#Connection.
+ *
+ * @param {Object}
+ * nodeTemplates, array of NodeTemplateShape
+ * @param {Object}
+ * subgraphEdges, array of jsPlumb#Connection
+ * @return {Object} array of jsPlumb#Connection which contains edges that aren't
+ * in the subgraph
+ */
+ function _computeEdgeComplement(nodeTemplates, subgraphEdges) {
+ var edgeSet = {};
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ var incomingEdges = _returnIncomingEdges(nodeTemplates[i]);
+ var outgoingEdges = _returnOutgoingEdges(nodeTemplates[i]);
+ for ( var incomingEdgeIndex = 0; incomingEdgeIndex < incomingEdges.length; incomingEdgeIndex++) {
+ var inSubgraph = false;
+ for ( var subgraphEdgeIndex = 0; subgraphEdgeIndex < subgraphEdges.length; subgraphEdgeIndex++) {
+ if (incomingEdges[incomingEdgeIndex] === subgraphEdges[subgraphEdgeIndex]) {
+ inSubgraph = true;
+ break;
+ }
+ }
+ if (!inSubgraph) {
+ edgeSet[incomingEdges[incomingEdgeIndex]] = incomingEdges[incomingEdgeIndex];
+ }
+ }
+ for ( var outgoingEdgeIndex = 0; outgoingEdgeIndex < outgoingEdges.length; outgoingEdgeIndex++) {
+ var inSubgraph = false;
+ for ( var subgraphEdgeIndex = 0; subgraphEdgeIndex < subgraphEdges.length; subgraphEdgeIndex++) {
+ if (outgoingEdges[outgoingEdgeIndex] === subgraphEdges[subgraphEdgeIndex]) {
+ inSubgraph = true;
+ break;
+ }
+ }
+ if (!inSubgraph) {
+ edgeSet[outgoingEdges[outgoingEdgeIndex]] = outgoingEdges[outgoingEdgeIndex];
+ }
+ }
+ }
+ // transform edge-set to array
+ var edgeArray = new Array();
+ for (edge in edgeSet) {
+ edgeArray.push(edgeSet[edge]);
+ }
+ return edgeArray;
+ }
+
+ /**
+ * Returns a list of edges which represent an acyclic subgraph of the given
+ * nodes. The graph is at least of size 1/2*|E|, which can be troublesome in
+ * some situations. This means the graph could be layouted only on the "half" of
+ * it.
+ *
+ * Info: There are more sophisticated methods that guarantee to produce bigger
+ * subgraphs, see "Drawing Graphs: Methods and Models". But in this state it is
+ * a huge undertake, cause Oryx doesn't seem to have any basic graph algorithm
+ * shipped, like DFS etc.(correct me if i'm wrong, please).
+ *
+ * @param {Object}
+ * nodes of a graph, which are NodeTemplatesShapes
+ * @return [Object] edges of the contained acyclic subgraph, which are
+ * jsPlumb#Connection Objects
+ * @see "Drawing Graphs: Methods and Models", p. 91
+ */
+ function _computeAcyclicSubgraph(nodeTemplates) {
+ var subgraphEdges = [];
+ for ( var i = 0; i < nodeTemplates.length; i++) {
+ if (_returnOutgoingEdges(nodeTemplates[i]).length >= _returnIncomingEdges(nodeTemplates[i]).length) {
+ for ( var j = 0; j < _returnOutgoingEdges(nodeTemplates[i]).length; j++) {
+ subgraphEdges.push(_returnOutgoingEdges(nodeTemplates[i])[j]);
+ }
+ } else {
+ for ( var j = 0; j < _returnIncomingEdges(nodeTemplates[i]).length; j++) {
+ subgraphEdges.push(_returnIncomingEdges(nodeTemplates[i])[j]);
+ }
+ }
+ }
+ var filteredArray = subgraphEdges.filter(function(elem, pos) {
+ return subgraphEdges.indexOf(elem) == pos;
+ });
+ return filteredArray;
+ }
+
+ /**
+ * Returns relations having the given NodeTemplate as target
+ *
+ * @param nodeTemplate
+ * a nodeTemplateShape
+ * @returns Returns an array containing jsPlumb#Connection objects representing
+ * relations
+ */
+ function _returnIncomingEdges(nodeTemplate) {
+ var edgeArray = new Array();
+ jsPlumb.select().each(
+ function(connection) {
+ if (connection.targetId === nodeTemplate.id) {
+ /*if (connection.getType().length != 0) {
+ for ( var i = 0; i < connection.getType().length; i++) {
+ if (connection.getType()[i].toLowerCase().indexOf(
+ "hostedon") !== -1) {
+ edgeArray.push(connection);
+ }
+ }
+ } else {
+ edgeArray.push(connection);
+ }*/
+ edgeArray.push(connection);
+
+ }
+ });
+ return edgeArray;
+ }
+
+ /**
+ * Returns relations having the given NodeTemplate as source
+ *
+ * @param nodeTemplate
+ * a nodeTemplateShape
+ * @returns Returns an array containing jsPlumb#Connections objects representing
+ * relations
+ */
+ function _returnOutgoingEdges(nodeTemplate) {
+ var edgeArray = new Array();
+ jsPlumb.select().each(
+ function(connection) {
+ if (connection.sourceId === nodeTemplate.id) {
+ /*if (connection.getType().length != 0) {
+ for ( var i = 0; i < connection.getType().length; i++) {
+ if (connection.getType()[i].toLowerCase().indexOf(
+ "hostedon") !== -1) {
+ edgeArray.push(connection);
+ }
+ }
+ } else {
+ edgeArray.push(connection);
+ }*/
+ edgeArray.push(connection);
+ }
+ });
+ return edgeArray;
+ }
+
+ /**
+ * Returns the NodeTemplates which can be reached by a outgoing edges of the given NodeTemplate
+ * @param nodeTemplate the NodeTemplate whose Succesors should be calculated
+ * @returns {Array} an array of NodeTemplates which are successors of the given NodeTemplate, may be empty
+ */
+ function _returnSuccessors(nodeTemplate){
+ var nodeArray = new Array();
+ var outgoingEdges = _returnOutgoingEdges(nodeTemplate);
+ for(var i =0; i < outgoingEdges.length; i++){
+ nodeArray.push(outgoingEdges[i].target);
+ }
+ return nodeArray;
+ }
+
+ /**
+ * Returns all sources of the given graph
+ * @param nodeTemplates vertices of the graph, as an array of NodeTemplates
+ * @returns {Array} an Array of NodeTemplates
+ */
+ function _returnSources(nodeTemplates){
+ var sourceArray = new Array();
+ for(var i = 0; i < nodeTemplates.length; i++){
+ if(_returnIncomingEdges(nodeTemplates[i]) == 0){
+ sourceArray.push(nodeTemplates[i]);
+ }
+ }
+ return sourceArray;
+ }
+
+ /**
+ * Reverses a relationshipTemplate
+ *
+ * @param relationshipTemplate
+ * as we don't have relationshiptemplates modelled here we expect
+ * jsPlumb#Connection objects
+ */
+ function _reverseEdge(relationshipTemplate) {
+ // this seems to work
+ var source = relationshipTemplate["source"];
+ var sourceId = relationshipTemplate["sourceId"];
+ var target = relationshipTemplate["target"];
+ var targetId = relationshipTemplate["targetId"];
+
+ relationshipTemplate["source"] = target;
+ relationshipTemplate["sourceId"] = targetId;
+ relationshipTemplate["target"] = source;
+ relationshipTemplate["targetId"] = sourceId;
+ }
+
+ /**
+ * Reverses relationshipTemplates
+ *
+ * @param relationshipTemplates
+ */
+ function _reverseEdges(relationshipTemplates) {
+ for ( var i = 0; i < relationshipTemplates.length; i++) {
+ _reverseEdge(relationshipTemplates[i]);
+ }
+ }
+}); \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js
new file mode 100644
index 0000000..4e87f22
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2014 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+/**
+ * Functions copied from winery-common.js to replace it in the long term
+ *
+ * Shared between topology modeler and repository
+ */
+define([], function() {
+ var xmlParser = new DOMParser();
+ var VALUE_OF_NONE_CHOOSEN = "(none)"; // constant to indicate that nothing is chosen in a select2
+
+ return {
+ encodeId: encodeId,
+ getNamespaceAndLocalNameFromQName: getNamespaceAndLocalNameFromQName,
+ replaceDialogShownHookForOrionUpdate: replaceDialogShownHookForOrionUpdate,
+ writeCollectionDefinedByATextArea: writeCollectionDefinedByATextArea,
+ getDocument: getDocument,
+
+ getURLFragmentOutOfFullQName: getURLFragmentOutOfFullQName,
+ makeArtifactTypeURLFromQName: makeArtifactTypeURLFromQName,
+ makeNodeTypeURLFromQName: makeNodeTypeURLFromQName,
+ makeRelationshipTypeURLFromQName: makeRelationshipTypeURLFromQName,
+ makeRelationshipTypeURLFromNSAndLocalName: makeRelationshipTypeURLFromNSAndLocalName,
+
+ qname2href: qname2href,
+
+ fetchSelect2DataAndInitSelect2: fetchSelect2DataAndInitSelect2,
+ removeItemFromSelect2Field: removeItemFromSelect2Field,
+
+ checkXMLValidityAndShowErrorIfInvalid: checkXMLValidityAndShowErrorIfInvalid,
+ synchronizeNameAndType: synchronizeNameAndType,
+
+ VALUE_OF_NONE_CHOOSEN: VALUE_OF_NONE_CHOOSEN
+ };
+
+ /**
+ * OriginalName: encodeID
+ */
+ function encodeId(id) {
+ // the URL sent to the server should be the encoded id
+ id = encodeURIComponent(id);
+ // therefore, we have to encode it twice
+ id = encodeURIComponent(id);
+ return id;
+ }
+
+ /**
+ * @param qname a QName in the form {namespace}localname
+ * @return { namespace: namespace, localname: localname }
+ */
+ function getNamespaceAndLocalNameFromQName(qname) {
+ var i = qname.indexOf("}");
+ var res = {
+ namespace : qname.substr(1,i-1),
+ localname : qname.substr(i+1)
+ };
+ return res;
+ }
+
+ /**
+ * Orion does not update content if field not fully shown
+ * therefore, we hook in into the "shown" event
+ */
+ function replaceDialogShownHookForOrionUpdate(diag, orionAreaId, content) {
+ diag.off("shown.bs.modal");
+ diag.on("shown.bs.modal", function() {
+ var area = window.winery.orionareas[orionAreaId];
+ area.editor.setText(content);
+ area.fixEditorHeight();
+ });
+ }
+
+ function getURLFragmentOutOfNSAndLocalName(nsAndLocalName) {
+ var res;
+ res = encodeID(nsAndLocalName.namespace);
+ res = res + "/";
+ res = res + encodeID(nsAndLocalName.localname);
+ return res;
+ }
+
+ /**
+ * Extracts an URL fragment of the form <encoded namespace>/<encoded id> out of a full QName
+ *
+ * @param qname a QName in the form {namespace}localname
+ */
+ function getURLFragmentOutOfFullQName(qname) {
+ var d = getNamespaceAndLocalNameFromQName(qname);
+ return getURLFragmentOutOfNSAndLocalName(d);
+ }
+
+ /**
+ * @param w the XMLwriter
+ * @param elementSet the set of HTML elements to write
+ * @param elementName the name of the wrapper element (e.g., "Requirements", "Policies")
+ */
+ function writeCollectionDefinedByATextArea(w, elementSet, elementName) {
+ if (elementSet.length !== 0) {
+ w.writeStartElement(elementName);
+ elementSet.each(function(i, element) {
+ // XML contains element completely
+ // we do not have to parse reqorcap.children("div.id").children("span.id").text() or the span.name
+ var text = $(element).children("textarea").val();
+ w.writeXML(text);
+ });
+ w.writeEndElement();
+ }
+ }
+
+ function makeArtifactTypeURLFromQName(repoURL, qname) {
+ return repoURL + "/artifacttypes/" + getURLFragmentOutOfFullQName(qname) + "/";
+ }
+
+ function makeNodeTypeURLFromQName(repoURL, qname) {
+ return repoURL + "/nodetypes/" + getURLFragmentOutOfFullQName(qname) + "/";
+ }
+
+ function makeRelationshipTypeURLFromQName(repoURL, qname) {
+ return repoURL + "/relationshiptypes/" + getURLFragmentOutOfFullQName(qname) + "/";
+ }
+
+ function makeRelationshipTypeURLFromNSAndLocalName(repoURL, nsAndLocalName) {
+ return repoURL + "/relationshiptypes/" + getURLFragmentOutOfNSAndLocalName(nsAndLocalName) + "/";
+ }
+
+ /**
+ * functionality similar to org.eclipse.winery.common.Util.qname2href(String, Class<? extends TExtensibleElements>, QName)
+ */
+ function qname2href(repositoryUrl, componentPathFragment, qname) {
+ var nsAndId = getNamespaceAndLocalNameFromQName(qname);
+ var absoluteURL = repositoryUrl + "/" + componentPathFragment + "/" + getURLFragmentOutOfNSAndLocalName(nsAndId);
+ var res = "<a target=\"_blank\" data-qname=\"" + qname + "\" href=\"" + absoluteURL + "\">" + nsAndId.localname + "</a>";
+ return res;
+ }
+
+ /**
+ * Inspired by
+ *
+ * @param field is the jquery field
+ * @param id_to_remove the id to remove
+ */
+ function removeItemFromSelect2Field(field, id_to_remove) {
+ // nothing can be done currently
+ // see https://github.com/ivaynberg/select2/issues/535#issuecomment-30210641 for a disucssion
+ vShowNotification("The select field shows stale data. Refresh the page to get rid of that.")
+ }
+
+ /**
+ * Fetches select2 data from the given URL and initializes the field provided by the fieldId
+ *
+ * Calls vShowError if something went wrong
+ *
+ * @param onSuccess (optional)
+ * @param allowAdditions (optional) if set to true, select2 is initalized with the functionality to allow additions during the search
+ */
+ function fetchSelect2DataAndInitSelect2(fieldId, url, onSuccess, allowAdditions) {
+ $.ajax({
+ url: url,
+ dataType: "json"
+ }).done(function (result) {
+ var params = {"data": result};
+ if (typeof allowAdditions === "boolean") {
+ params.createSearchChoice = function(term) {
+ // enables creation of new namespaces
+ return {id:term, text:term};
+ }
+ }
+
+ // init select2 and select first item
+ $("#" + fieldId).select2(params);
+ if (result.length === 0) {
+ $("#" + fieldId).select2("val", null);
+ } else {
+ $("#" + fieldId).select2("val", result[0].id);
+ }
+
+ if (typeof onSuccess === "function") {
+ onSuccess();
+ }
+ }).fail(function(jqXHR, textStatus, errorThrown) {
+ vShowAJAXError("Could not fetch select2 data from " + url, jqXHR, errorThrown);
+ });
+
+ }
+
+
+ function getDocument(xmlString) {
+ var xmlDoc = xmlParser.parseFromString(xmlString, "text/xml");
+ return xmlDoc;
+ }
+
+ /**
+ * Checks given XML string for validity. If it's invalid, an error message is shown
+ * Relies on the current browser's XML handling returning a HTML document if something went wrong during parsing
+ *
+ * @return XMLdocument if XML is valid, false otherwise
+ */
+ function checkXMLValidityAndShowErrorIfInvalid(xmlString) {
+ var doc = getDocument(xmlString);
+ var errorMsg = "";
+ if (doc.firstChild.localName == "html") {
+ errorMsg = new XMLSerializer().serializeToString(doc);
+ } else {
+ // at Chrome, the error may be nested in the XML
+
+ // quick hack, only "xhtml" is quered
+ function nsResolover(x) {
+ return "http://www.w3.org/1999/xhtml";
+ }
+
+ var element = doc.evaluate( '//x:parsererror', doc, nsResolover, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ if (element !== null) {
+ errorMsg = new XMLSerializer().serializeToString(element);
+ }
+ }
+
+ if (errorMsg !== "") {
+ vShowError(errorMsg);
+ return false;
+ } else {
+ return doc;
+ }
+ }
+
+ /**
+ * Updates the XML in the orion editor based on the values given in the input fields.
+ * Shows error if XML is invalid
+ *
+ * Works only with SELECT2 fields
+ *
+ * @param idPrefix: (new|edit)${shortName}, derived names: [idPrefix]Name, [idPrefix]Id, Orion[idPrefix]XML
+ * @param hasIdField: whether the Id should be read and written
+ * @param selectFields: array of {attribute, fieldSuffix}, where attribute is a name of an attribute having a QName.
+ * Each select box is determined by #[idPrefix][fieldSuffix].
+ * The select box content is converted to a QName and the result is written to the attribute [name]
+ * Default: {attribute: "type", fieldSuffix: "Type"}
+ *
+ * @return false if XML is invalid, true: an object if id/name/attribute1/attribute2/... (qname + attribute1FullQName: qname object)/xml (to be used in tmpl-${cssClassPrefix})
+ * {id:id, name:name, type: "ns5:type", typeFullQName: "{http://www.example.org}type"}
+ */
+ function synchronizeNameAndType(idPrefix, hasIdField, selectFields) {
+ if (typeof hasIdField === undefined) {
+ hasIdField = true;
+ }
+ if (typeof selectFields === undefined) {
+ selectFields = [{attribute: "type", fieldSuffix: "Type"}];
+ }
+
+
+ var val = window.winery.orionareas["Orion" + idPrefix + "XML"].editor.getText();
+ var xmlDoc = checkXMLValidityAndShowErrorIfInvalid(val);
+ if (xmlDoc) {
+ // handle name
+ var name = $("#" + idPrefix + "Name").val();
+ // initialize result object
+ var res = {
+ name: name
+ };
+ xmlDoc.firstChild.setAttribute("name", name);
+
+ // write id and name to XML
+ if (hasIdField) {
+ var id = $("#" + idPrefix + "Id").val();
+ if (!id) {
+ // TODO a checking should be done if the id exists
+ // probably not here, but at caller's side
+ id = name;
+ }
+ xmlDoc.firstChild.setAttribute("id", id);
+ res.id = id;
+ }
+
+ // write each selectField to xml
+ // for that, we have to determine the QName
+ $(selectFields).each(function(i, m) {
+ var content = $("#" + idPrefix + m.fieldSuffix).select2("val");
+
+ if (content == VALUE_OF_NONE_CHOOSEN) {
+ // if nothing is chosen do not put it into the result
+ return;
+ }
+
+ // determine qname of type
+ //getQNameOutOfFullQName(type, xmlDoc.firstChild) does not always work as xmlDoc.firstChild does not have ALL *available* namespace prefixes
+ var typeNSAndId = getNamespaceAndLocalNameFromQName(content);
+ var prefix = xmlDoc.firstChild.lookupPrefix(typeNSAndId.namespace);
+ if (!prefix) {
+ // we have to ask the repo for a prefix
+ $.ajax({
+ type: "GET",
+ async: false,
+ "url": winery.repositoryURL + "/admin/namespaces/" + encodeID(typeNSAndId.namespace),
+ dataType: "text",
+ error: function(jqXHR, textStatus, errorThrown) {
+ vShowAJAXError("Could not determine prefix", jqXHR, errorThrown);
+ },
+ success: function(resData, textStatus, jqXHR) {
+ prefix = resData;
+ }
+ });
+ // new prefix fetched, xmlns attribute has to be written
+ xmlDoc.firstChild.setAttribute("xmlns:" + prefix, typeNSAndId.namespace);
+ }
+ var qname = prefix + ":" + typeNSAndId.localname;
+ res[m.attribute] = qname;
+ res[m.attribute + "FullQName"] = typeNSAndId;
+ xmlDoc.firstChild.setAttribute(m.attribute, qname);
+ });
+
+ var xml = new XMLSerializer().serializeToString(xmlDoc);
+ res.xml = xml;
+
+ return res;
+ } else {
+ return false;
+ }
+ }
+
+
+ }
+);
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js
new file mode 100644
index 0000000..6ab66e8
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Pascal Hirmer.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Pascal Hirmer - initial API and implementation
+ *******************************************************************************/
+
+define(function() {
+
+ /**
+ * This module handles a click on "Complete Topology" in the enterTopologyCompletionInformation dialog.
+ */
+ var module = {
+ complete: complete,
+ restartCompletion: restartCompletion
+ };
+ return module;
+
+ /**
+ * This function start the topology completion. With information from the
+ * PolicyInformationDialog the topologyCompletion.jsp is called which will invoke the
+ * Topology Completion java component. The parameters determine if the current topology
+ * will be overwritten or if a new topology is created.
+ *
+ * @param (String) overwriteTopology
+ * determines how to save the topology. If true, the current topology is overwritten.
+ * @param (Boolean) openInNewWindow
+ * determines if the result is opened in a new window
+ * @param (String) topologyName
+ * the name of the new topology
+ * @param (String) topologyNamespace
+ * the namespace of the new topology
+ * @param (Boolean) stepByStep
+ * if true, the completion will be processed step by step
+ * @param (String) repositoryURL
+ * the URL to the repository the topology is saved to
+ * @param (String) toscaNamespace
+ * the namespace URL of TOSCA
+ * @param (String) wineryNamespace
+ * the namespace URL of winery
+ * @param (String) serviceTemplateName
+ * the name of the service template containing the displayed topology template
+ * @param (String) topologyTemplateURL
+ * the URL to the displayed topology template
+ */
+ function complete(overwriteTopology, openInNewWindow, topologyName, topologyNamespace, stepByStep, repositoryURL, serviceTemplateName, topologyTemplateURL) {
+
+ // if the user wants to create a new topology, a post call is sent to the repository adding
+ // the new ServiceTemplate
+ if (!overwriteTopology) {
+ var dataToSend = "name=" + topologyName + "&namespace=" + topologyNamespace;
+ var url = repositoryURL + "/servicetemplates/";
+ $.ajax(
+ {
+ type: "POST",
+ async: false,
+ url: url,
+ "data": dataToSend,
+ dataType: "text",
+ error: function(jqXHR, textStatus, errorThrown) {
+ vShowAJAXError("Could not add Service Template.");
+ }
+ }
+ );
+ }
+
+ var topology;
+ require(["winery-topologymodeler-AMD"], function(wt) {
+ topology = wt.getTopologyTemplateAsXML(true);
+
+ // call to the completion JSP which will call the java component
+ $.post("jsp/topologyCompletion/topologyCompletion.jsp", {topology: topology, stName: serviceTemplateName, templateURL: topologyTemplateURL, overwriteTopology: overwriteTopology, topologyName: topologyName, topologyNamespace: topologyNamespace, repositoryURL: repositoryURL, stepByStep: stepByStep, openInNewWindow: openInNewWindow, restarted: "false"},
+ /**
+ * Callback function which will either open a new window if a new topology was created or
+ * refresh the current browser window
+ *
+ * @param (String) data
+ * the answer of the post call
+ */
+ function(data){
+ // checks the message returned by the CompletionInterface
+ if (data.indexOf("topologyComplete") != -1) {
+ vShowSuccess('The topology is already complete.');
+ } else if (data.indexOf("failure") != -1) {
+ vShowError(data);
+ } else if (data.indexOf("userTopologySelection") != -1) {
+ $(chooseTopologyDiag[0].children[0].children[0].children[1]).html(data);
+ window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION);
+ chooseTopologyDiag.modal("show");
+ } else if (data.indexOf("topologyComplete") == -1 && data.indexOf("userTopologySelection") == -1 && data.indexOf("userInteraction") != -1) {
+ $(chooseRelationshipTemplateDiag[0].children[0].children[0].children[1]).html(data);
+ window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION);
+ chooseRelationshipTemplateDiag.modal("show");
+ } else if (data.indexOf("topologyComplete") == -1 && data.indexOf("stepByStep") != -1) {
+ $(chooseNodeTemplateDiag[0].children[0].children[0].children[1]).html(data);
+ window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION);
+ chooseNodeTemplateDiag.modal("show");
+ } else {
+ if (openInNewWindow) {
+ // a new topology has been created, open it in a new window
+ var win=window.open('?repositoryURL=' + repositoryURL + '&ns='+ topologyNamespace + '&id=' + topologyName, '_blank');
+ win.focus();
+ } else if (overwriteTopology) {
+ // refresh page
+ document.location.reload(true);
+ }
+ }
+ }
+ );
+ });
+ }
+
+ /**
+ * This function restarts the topology completion when it has been stopped to get a
+ * user decision.
+ *
+ * @param (String) topology
+ * the topology as XML string
+ * @param (String) overwriteTopology
+ * determines how to save the topology. Can contain the values "createNew" or "overwrite"
+ * @param (Boolean) openInNewWindow
+ * determines if the result is opened in a new window
+ * @param (String) topologyName
+ * the name of the new topology
+ * @param (String) topologyNamespace
+ * the namespace of the new topology
+ * @param (Boolean) stepByStep
+ * if true, the completion will be processed step by step
+ * @param (String) serviceTemplateName
+ * the name of the service template containing the displayed topology template
+ * @param (String) topologyTemplateURL
+ * the URL to the displayed topology template
+ * @param (String) repositoryURL
+ * the URL to the repository the topology is saved to
+ */
+ function restartCompletion(topology, overwriteTopology, openInNewWindow, topologyName, topologyNamespace, stepByStep, serviceTemplateName, topologyTemplateURL, repositoryURL) {
+
+ // remove whitespaces in the topology XML string
+ topology = topology.replace(/\s+/g, ' ');
+ topology = topology.substr(1);
+
+ // call to the completion JSP which will call the java component
+ $.post("jsp/topologyCompletion/topologyCompletion.jsp", { topology: topology, stName: serviceTemplateName, templateURL: topologyTemplateURL, overwriteTopology: overwriteTopology, topologyName: topologyName, topologyNamespace: topologyNamespace, repositoryURL: repositoryURL, stepByStep: stepByStep, restarted: "true"},
+ /**
+ * Callback function which will either open a new window if a new topology was created or
+ * refresh the current browser window
+ *
+ * @param (String) data
+ * the answer of the post call
+ */
+ function(data){
+ if (data.indexOf("Successful") == -1 && data.indexOf("stepByStep") == -1) {
+ $(chooseRelationshipTemplateDiag[0].children[0].children[0].children[1]).html(data);
+ window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION);
+ chooseRelationshipTemplateDiag.modal("show");
+ } else if (data.indexOf("Successful") == -1 && data.indexOf("stepByStep") != -1) {
+ $(chooseNodeTemplateDiag[0].children[0].children[0].children[1]).html(data);
+ window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION);
+ chooseNodeTemplateDiag.modal("show");
+ } else {
+ if (openInNewWindow) {
+ // a new topology has been created, open it in a new window
+ var win = window.open('?repositoryURL=' + "<%=repositoryURL%>" + '&ns='+ topologyNamespace + '&id=' + topologyName, '_blank');
+ win.focus();
+ } else if (overwriteTopology) {
+ // refresh page
+ document.location.reload(true);
+ }
+ }
+ }
+ );
+ }
+}); \ No newline at end of file
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js
new file mode 100644
index 0000000..a292f43
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2015 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ *******************************************************************************/
+
+/**
+ * This file contains supporting functions for the topoplogy modeler
+ */
+define(
+ // although XMLWriter ist not an AMD module, requirejs does not complain when loading it
+ ["winery-support-common", "XMLWriter"],
+ function (w) {
+ // has to be consistent with {@link org.eclipse.winery.common.constants.Namespaces}
+ var TOSCA_NAMESPACE = "http://docs.oasis-open.org/tosca/ns/2011/12";
+ var TOSCA_WINERY_EXTENSIONS_NAMESPACE ="http://www.opentosca.org/winery/extensions/tosca/2013/02/12";
+
+ var topologyTemplateURL;
+
+ var module = {
+ save: save,
+ setTopologyTemplateURL: function(url) {
+ topologyTemplateURL = url;
+ },
+ getTopologyTemplateAsXML: getTopologyTemplateAsXML,
+
+ TOSCA_NAMESPACE: TOSCA_NAMESPACE,
+ TOSCA_WINERY_EXTENSIONS_NAMESPACE: TOSCA_WINERY_EXTENSIONS_NAMESPACE
+ };
+ return module;
+
+ function writeReqOrCaps(elements, xmlw, globalWrapperElementName, singleElementWrapperName) {
+ if (elements.length != 0) {
+ xmlw.writeStartElement(globalWrapperElementName);
+
+ $.each(elements, function(i,e) {
+ xmlw.writeStartElement(singleElementWrapperName);
+ e = $(e);
+ xmlw.writeAttributeString("id", e.children(".id").text());
+ xmlw.writeAttributeString("name", e.children(".name").text());
+ writeType(xmlw, e.children(".type").children("a").data("qname"));
+ savePropertiesFromDivToXMLWriter(e.children("div.propertiesContainer"), xmlw);
+ xmlw.writeEndElement();
+ });
+
+ xmlw.writeEndElement();
+ }
+
+ }
+
+ /**
+ * "doSave"
+ */
+ function save() {
+ $("#saveBtn").button('loading');
+
+ $.ajax({
+ url: topologyTemplateURL,
+ type: "PUT",
+ contentType: 'text/xml',
+ data: getTopologyTemplateAsXML(false),
+ success: function(data, textStatus, jqXHR) {
+ $("#saveBtn").button('reset');
+ vShowSuccess("successfully saved.");
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ $("#saveBtn").button('reset');
+ vShowAJAXError("Could not save", jqXHR, errorThrown);
+ }
+ });
+ }
+
+ /**
+ * Creates an XML String of the modelled topology template.
+ */
+ function getTopologyTemplateAsXML(needsDefinitionsTag) {
+
+ var xmlw = new XMLWriter("utf-8");
+ xmlw.writeStartDocument();
+
+ if (needsDefinitionsTag) {
+ xmlw.writeStartElement("Definitions");
+ xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
+ xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
+
+ xmlw.writeStartElement("ServiceTemplate");
+ xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
+ xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
+ }
+ xmlw.writeStartElement("TopologyTemplate");
+ xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE);
+ xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE);
+ $("div.NodeTemplateShape").not(".hidden").each (function() {
+ xmlw.writeStartElement("NodeTemplate");
+
+ var id = $(this).attr("id");
+
+ var headerContainer = $(this).children("div.headerContainer");
+ var name = headerContainer.children("div.name").text();
+ var typeQNameStr = headerContainer.children("span.typeQName").text();
+ var minmaxdiv = headerContainer.children("div.minMaxInstances");
+ var min = minmaxdiv.children("span.minInstances").text();
+ var max = minmaxdiv.children("span.maxInstances").text();
+ if (max == "∞") {
+ max = "unbounded";
+ }
+ var x = $(this).css("left");
+ x = x.substring(0, x.indexOf("px"));
+ var y = $(this).css("top");
+ y = y.substring(0, y.indexOf("px"));
+
+ xmlw.writeAttributeString("id", id);
+ if (name != "") {
+ xmlw.writeAttributeString("name", name);
+ }
+ writeType(xmlw, typeQNameStr);
+ if (min != "") {
+ xmlw.writeAttributeString("minInstances", min);
+ }
+ if (max != "") {
+ xmlw.writeAttributeString("maxInstances", max);
+ }
+ xmlw.writeAttributeString("winery:x", x);
+ xmlw.writeAttributeString("winery:y", y);
+
+ /** Properties **/
+ savePropertiesFromDivToXMLWriter($(this).children("div.propertiesContainer"), xmlw);
+
+ /** Requirements **/
+ writeReqOrCaps(
+ $(this).children("div.requirementsContainer").children("div.content").children("div.reqorcap"),
+ xmlw,
+ "Requirements",
+ "Requirement");
+
+ /** Capabilities **/
+ writeReqOrCaps(
+ $(this).children("div.capabilitiesContainer").children("div.content").children("div.reqorcap"),
+ xmlw,
+ "Capabilities",
+ "Capability");
+
+ /** Policies **/
+ w.writeCollectionDefinedByATextArea(xmlw,
+ $(this).children("div.policiesContainer").children("div.content").children("div.policy"),
+ "Policies");
+
+ /** Deployment Artifacts **/
+ var das = $(this).children("div.deploymentArtifactsContainer").children("div.content").children("div.deploymentArtifact");
+ if (das.length != 0) {
+ xmlw.writeStartElement("DeploymentArtifacts");
+ das.each(function(i,e) {
+ // the textarea contains a valid deployment artifact xml
+ var xml = $(e).children("textarea").val();
+ xmlw.writeXML(xml);
+ });
+ xmlw.writeEndElement();
+ }
+
+ // End: Nodetemplate
+ xmlw.writeEndElement();
+ });
+ jsPlumb.select().each(function(connection) {
+ xmlw.writeStartElement("RelationshipTemplate");
+ var id = connection.id;
+ var typeQNameStr = connection.getType()[0];
+
+ var connData = winery.connections[id];
+ if (!connData) {
+ vShowError("Error in the internal data structure: Id " + id + " not found");
+ return;
+ }
+
+ xmlw.writeAttributeString("id", connData.id);
+ if (connData.name != "") {
+ xmlw.writeAttributeString("name", connData.name);
+ }
+ writeType(xmlw, typeQNameStr);
+
+ if (typeof connData.propertiesContainer !== "undefined") {
+ savePropertiesFromDivToXMLWriter(connData.propertiesContainer, xmlw);
+ }
+
+ xmlw.writeStartElement("SourceElement");
+ if (connData.req) {
+ // conn starts at a requirement
+ xmlw.writeAttributeString("ref", connData.req);
+ } else {
+ // conn starts at a node template
+ xmlw.writeAttributeString("ref", connection.sourceId);
+ }
+ xmlw.writeEndElement();
+ xmlw.writeStartElement("TargetElement");
+ if (connData.cap) {
+ // conn ends at a capability
+ xmlw.writeAttributeString("ref", connData.cap);
+ } else {
+ // conn ends at a node template
+ xmlw.writeAttributeString("ref", connection.targetId);
+ }
+ xmlw.writeEndElement();
+
+ xmlw.writeEndElement();
+ });
+
+ if (needsDefinitionsTag) {
+ xmlw.writeEndElement();
+ xmlw.writeEndElement();
+ }
+
+ xmlw.writeEndDocument();
+
+ return xmlw.flush();
+ }
+
+ function writeQNameAttribute(w, nsPrefix, qnameStr) {
+ var qname = getQName(qnameStr);
+ w.writeAttributeString("xmlns:" + nsPrefix, qname.namespace);
+ w.writeAttributeString("type", nsPrefix + ":" + qname.localName);
+ }
+
+ function writeType(w, typeQNameStr) {
+ writeQNameAttribute(w, "ty", typeQNameStr);
+ }
+
+ }
+);
+
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js
new file mode 100644
index 0000000..cc5c564
--- /dev/null
+++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js
@@ -0,0 +1,308 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2013,2015 University of Stuttgart.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and the Apache License 2.0 which both accompany this distribution,
+ * and are available at http://www.eclipse.org/legal/epl-v10.html
+ * and http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Contributors:
+ * Oliver Kopp - initial API and implementation and/or initial documentation
+ * Yves Schubert - switch to bootstrap 3
+ *******************************************************************************/
+
+function getQName(qnameStr) {
+ var pos = qnameStr.indexOf("}");
+ var namespace = qnameStr.substring(1, pos);
+ var localName = qnameStr.substring(pos+1);
+ var res = {
+ "namespace": namespace,
+ "localName": localName
+ };
+ return res;
+}
+
+/**
+ * @param attributeName an attribute with a value in the form prefix:localname
+ * @param xmlElement a DOM element (offering the method lookupNamespaceURI)
+ * @return { ns: namespace, id: id }
+ */
+function getNSAndId(attributeName, xmlElement) {
+ var attributeValue = xmlElement.getAttribute(attributeName);
+ var i = attributeValue.indexOf(":");
+ var prefix = attributeValue.substring(0, i);
+ var localName = attributeValue.substring(i+1);
+ var ns = xmlElement.lookupNamespaceURI(prefix);
+ var res = {
+ ns : ns,
+ id: localName
+ };
+ return res;
+}
+
+/**
+ * @param el a href element
+ * @param pathComponent the path element "artifacttemplates" or "artifacttypes"
+ * @param attributeName the name of the attribute to read from the given xmlElement
+ * @param xmlElement used to resolve a namespace prefix to a full namespace URI
+ */
+function addHref(el, pathComponent, attributeName, xmlElement) {
+ var nsAndId = getNSAndId(attributeName, xmlElement);
+ var loc = winery.repositoryURL + "/" + pathComponent + "/" + encodeID(nsAndId.ns) + "/" + encodeID(nsAndId.id);
+ el.attr("href", loc);
+}
+
+var currentlySelectedDeploymentArtifactDiv;
+
+/**
+ * Sets global variables currentlySelectedNodeTemplate and currentlySelectedDeploymentArtifactDiv
+ */
+function showDeploymentArtifactInformation(nodeTemplateId, deploymentArtifactName) {
+ currentlySelectedNodeTemplate = nodeTemplateId;
+ var daDiv = $("#" + nodeTemplateId).children("div.deploymentArtifactsContainer").children("div.content").children("div.deploymentArtifact").children("div.name:contains(" + deploymentArtifactName + ")").parent();
+ currentlySelectedDeploymentArtifactDiv = daDiv;
+ var xml = daDiv.children("textarea").val();
+
+ // get values to display directly from the "UI" instead of parsing the XML and asking the server for appropriate names
+ var daArtifactTemplateName = daDiv.children("div.artifactTemplate").text();
+ var daArtifactTypeName = daDiv.children("div.artifactType").text();
+
+ // determine URLs
+ require(["winery-support-common"], function(wsc) {
+ xmlDoc = wsc.getDocument(xml);
+ da = xmlDoc.firstChild;
+
+ $("#DAname").text(deploymentArtifactName);
+
+ $("#DAArtifactType").text(daArtifactTypeName);
+ addHref($("#DAArtifactType"), "artifacttypes", "artifactType", da);
+
+ var at = $("#DAArtifactTemplate");
+ if (daArtifactTemplateName != "") {
+ at.text(daArtifactTemplateName);
+ addHref(at, "artifacttemplates", "artifactRef", da);
+ } else {
+ at.text("No template associated");
+ at.removeAttr("href");
+ }
+
+ $("#DAXML").val(xml);
+
+ $("#DeploymentArtifactInfo").modal("show");
+ });
+}
+
+/**
+ * Adds the given data to the deployment artifacts table of the currently active node template
+ *
+ * @param xmlAsDOM XML DOM document, TDeploymentArtifact. Produced by org.eclipse.winery.resources.artifacts.GenericArtifactsResource.onPost(String, String, String, String, String, String, String, String)
+ * @param xmlAsString
+ */
+function addDeploymentArtifact(xmlAsDOM, xmlAsString) {
+ var da = xmlAsDOM.firstChild;
+ var daName = da.getAttribute("name");
+
+ // we do NOT extract artifactType / artifactTemplate from the XML, but use the user input
+ // showDeploymentArtifactInformation will extract its data directly from the XML without querying some input at the other HTML elements
+ var daArtifactTemplateName = $("#artifactTemplateName").val();
+ var daArtifactTypeName = $("#artifactType option:selected").text();
+
+ // add information to node template shape
+ var daData = {
+ nodeTemplateId : currentlySelectedNodeTemplate,
+ name : daName,
+ xml : xmlAsString,
+ artifactTypeName: daArtifactTypeName
+ };
+ if (daArtifactTemplateName != "") {
+ daData.artifactTemplateName = daArtifactTemplateName;
+ }
+ addDeploymentArtifactInfoToNodeTemplate(daData);
+}
+
+function addDeploymentArtifactInfoToNodeTemplate(daData) {
+ require(["tmpl"], function(tmpl){
+ var data = tmpl("tmpl-deploymentArtifact", daData);
+ var element = $("#" + currentlySelectedNodeTemplate).children(".deploymentArtifactsContainer").children(".content").children(".addDA:first");
+ element.before(data);
+ });
+}
+
+/**
+ * This function directly accesses the fields of the dialog, because the return value of the server is XML and we do not want to parse XML
+ *
+ * @param artifactInfo = {name, interfaceName (may be undefined), operationName (may be undefined), artifactTemplate (QName, may be undefined), artifactType}
+ */
+function artifactAddedSuccessfully(artifactInfo) {
+ var typeNsAndId = getNamespaceAndLocalNameFromQName(artifactInfo.artifactType);
+ var artifactTemplateNSAndId;
+ if (artifactInfo.artifactTemplate) {
+ artifactTemplateNSAndId = getNamespaceAndLocalNameFromQName(artifactInfo.artifactTemplate);
+ } else {
+ artifactTemplateNSAndId = undefined;
+ }
+
+ var daData = {
+ nodeTemplateId : currentlySelectedNodeTemplate,
+ name : artifactInfo.name,
+ artifactTypeName: typeNsAndId.localname,
+ artifactTypeNSAndId: typeNsAndId,
+ artifactTemplateName: artifactInfo.artifactTemplateName,
+ artifactTemplateNSAndId: artifactTemplateNSAndId
+ };
+ require(["tmpl"], function(tmpl){
+ daData.xml = tmpl("tmpl-deploymentArtifactXML", daData);
+ addDeploymentArtifactInfoToNodeTemplate(daData);
+ });
+}
+
+// variables used for creation of deployment artifacts
+var artifactTemplateAutoCreationEnabled = true;
+var syncDAnameWithATname;
+
+// introduced by the handling of deployment and implementation artifacts
+// holds the ID only (!)
+var currentlySelectedNodeTemplate;
+
+/**
+ * FIXME: this function is not updated to the the new dialog design and not included any more
+ *
+ * It should be used if the checkbox for at creation changes its checked status or if the at name is not valid
+ *
+ */
+function updateArtifactTemplateCreationEnablement(value) {
+ // remove field highlights
+ // (currently, no intelligent removal and addition is made)
+ $("#artifactName").removeClass("highlight");
+ $("#artifactTemplateName").removeClass("highlight");
+
+ if (value) {
+ // enable it
+ artifactTemplateAutoCreationEnabled = true;
+ $("#artifactTemplateName").removeAttr("disabled");
+ $("#artifactTemplateNS").removeAttr("disabled");
+ $("#createWithoutFilesBtn").attr("disabled", "disabled");
+ $("#createWithFilesBtn").removeAttr("disabled");
+ } else {
+ // disable it
+ artifactTemplateAutoCreationEnabled = false;
+ $("#artifactTemplateName").attr("disabled", "disabled");
+ $("#artifactTemplateNS").attr("disabled", "disabled");
+ $("#createWithoutFilesBtn").removeAttr("disabled");
+ $("#createWithFilesBtn").attr("disabled", "disabled");
+ }
+}
+
+function isShownNodeTemplateShapeChangeBoxes(shape) {
+ return (shape.find(".endpointContainer").is(":visible"));
+}
+
+/**
+ * @param shape jQuery object
+ */
+function showNodeTemplateShapeChangeBoxes(shape) {
+ shape.find(".addDA").show();
+ shape.children(".endpointContainer").show();
+ shape.find(".addnewreqorcap").show();
+ shape.find(".addnewpolicy").show();
+}
+
+/**
+ * @param shape jQuery object
+ */
+function hideNodeTemplateShapeChangeBoxes(shape) {
+ shape.find(".addDA").hide();
+ shape.children(".endpointContainer").hide();
+ shape.find(".addnewreqorcap").hide();
+ shape.find(".addnewpolicy").hide();
+}
+
+// indicates if a connection is currently drawn
+// used to decide whether the node template boxes should be displayed
+var isInConnectionMode = false;
+
+function wineryMoveSelectedNodeTemplateShapes(dX, dY) {
+ var shapes = $("div.NodeTemplateShape.selected");
+ hideNodeTemplateShapeChangeBoxes(shapes);
+ shapes.each(function(i, nodeTemplate) {
+ nodeTemplate = $(nodeTemplate);
+ var offset = nodeTemplate.offset();
+ offset.left += dX;
+ offset.top += dY;
+ nodeTemplate.offset(offset);
+ });
+ jsPlumb.repaint(shapes);
+}
+
+
+/**
+ * Simple eventing framework
+ *
+ * use
+ * winery.events.register(name, function) to register on an event
+ * and
+ * winery.events.fire(name) to fire all registered functions
+ */
+
+winery = {};
+winery.events = {
+ _events : {},
+
+ /**
+ * Registers a function
+ *
+ * @return the registered function
+ */
+ register : function(eventName, f) {
+ if (!winery.events._events[eventName]) {
+ winery.events._events[eventName] = {};
+ }
+ winery.events._events[eventName][f] = f;
+ return f;
+ },
+
+ /**
+ * Fires all functions associated with the given event name
+ */
+ fire : function(eventName) {
+ if (winery.events._events[eventName]) {
+ $.each(winery.events._events[eventName], function(index, value) {
+ value();
+ });
+ }
+ return true;
+ }
+};
+
+/**
+ * Determines whether a key combo is allowed.
+ *
+ * For instance, when a modal dialog is opened or a input is selected, DEL should not delete node template shapes
+ */
+function keyComboAllowed() {
+ return ((!$(document.activeElement).is("input")) && ($("div.modal:visible").size() == 0));
+}
+
+function keyComboAllowedAndNodeTemplatesSelected() {
+ return (keyComboAllowed() && ($("div.NodeTemplateShape.selected").size() != 0));
+}
+
+
+
+
+/* list of event names */
+winery.events.name = {};
+winery.events.name.command = {};
+
+winery.events.name.SELECTION_CHANGED = "selectionchanged";
+winery.events.name.command.SELECT_ALL_NODETEMPLATES = "selectAllNodeTemplates";
+winery.events.name.command.UNSELECT_ALL_NODETEMPLATES = "unselectAllNodeTemplates";
+winery.events.name.command.DELETE_SELECTION = "deleteSelection";
+
+winery.events.name.command.MOVE_DOWN = "moveDown";
+winery.events.name.command.MOVE_UP = "moveUp";
+winery.events.name.command.MOVE_LEFT = "moveLeft";
+winery.events.name.command.MOVE_RIGHT = "moveRight";
+
+winery.events.name.command.SAVE = "save";
+