summaryrefslogtreecommitdiffstats
path: root/sca-java-2.x/trunk/modules/node-manager/src
diff options
context:
space:
mode:
authorlresende <lresende@13f79535-47bb-0310-9956-ffa450edef68>2011-03-29 18:26:07 +0000
committerlresende <lresende@13f79535-47bb-0310-9956-ffa450edef68>2011-03-29 18:26:07 +0000
commitc6297b85e1e6e88dafd19d284bb4b7c1d8466c57 (patch)
tree04cc8889488854d9a2b04549e55340f9bd983402 /sca-java-2.x/trunk/modules/node-manager/src
parent7c6da5191ac78103fb5bab26a8c1d0c6240b062b (diff)
Simple strawman to provide some management rest services for nodes.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1086674 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'sca-java-2.x/trunk/modules/node-manager/src')
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/DomainCompositeResource.java38
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/impl/DomainCompositeResourceImpl.java109
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/main/resources/META-INF/services/org.apache.tuscany.sca.node.extensibility.NodeActivator18
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/java/org/apache/tuscany/sca/node/manager/DomainCompositeResourceTestCase.java78
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/node-manager-test.composite40
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/atomutil.js165
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/component.js443
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/elemutil.js257
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/graph.js1913
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/index.html90
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/jsonutil.js259
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/scdl.js243
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.css217
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.js333
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uiblue.css173
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uicyan.css217
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/util.js343
-rw-r--r--sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/xmlutil.js262
18 files changed, 5198 insertions, 0 deletions
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/DomainCompositeResource.java b/sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/DomainCompositeResource.java
new file mode 100644
index 0000000000..ecfaf254d7
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/DomainCompositeResource.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tuscany.sca.node.manager;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.oasisopen.sca.annotation.Remotable;
+
+@Remotable
+public interface DomainCompositeResource {
+
+ @GET
+ @Path("{domainURI}")
+ //@Produces(MediaType.TEXT_PLAIN)
+ String getDomainComposite(@PathParam("domainURI") @DefaultValue("default") String domainURI);
+}
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/impl/DomainCompositeResourceImpl.java b/sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/impl/DomainCompositeResourceImpl.java
new file mode 100644
index 0000000000..6e98315bbc
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/main/java/org/apache/tuscany/sca/node/manager/impl/DomainCompositeResourceImpl.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tuscany.sca.node.manager.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ws.rs.WebApplicationException;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLOutputFactory;
+
+import org.apache.tuscany.sca.assembly.Component;
+import org.apache.tuscany.sca.assembly.Composite;
+import org.apache.tuscany.sca.contribution.processor.ProcessorContext;
+import org.apache.tuscany.sca.contribution.processor.StAXArtifactProcessor;
+import org.apache.tuscany.sca.contribution.processor.StAXArtifactProcessorExtensionPoint;
+import org.apache.tuscany.sca.core.ExtensionPointRegistry;
+import org.apache.tuscany.sca.core.FactoryExtensionPoint;
+import org.apache.tuscany.sca.node.Node;
+import org.apache.tuscany.sca.node.extensibility.NodeActivator;
+import org.apache.tuscany.sca.node.extensibility.NodeExtension;
+import org.apache.tuscany.sca.node.manager.DomainCompositeResource;
+
+public class DomainCompositeResourceImpl implements NodeActivator, DomainCompositeResource {
+ private static Map<String, NodeExtension> nodeMap = new ConcurrentHashMap<String,NodeExtension>();
+
+ public void nodeStarted(Node node) {
+ NodeExtension nodeExtension = (NodeExtension) node;
+ nodeMap.put(nodeExtension.getDomainURI(), nodeExtension);
+ }
+
+ public void nodeStopped(Node node) {
+ NodeExtension nodeExtension = (NodeExtension) node;
+ nodeMap.remove(nodeExtension.getDomainURI());
+ }
+
+ public String getDomainComposite(String domainURI) {
+ System.out.println(">>> getDomainComposite");
+
+ if( ! nodeMap.containsKey(domainURI)) {
+ throw new WebApplicationException(404);
+ }
+
+ NodeExtension node = nodeMap.get(domainURI);
+ Composite composite = node.getDomainComposite();
+
+ //set name, as it's empty by default
+ composite.setName(new QName("", "Domain"));
+
+ ExtensionPointRegistry registry = node.getExtensionPointRegistry();
+ StAXArtifactProcessorExtensionPoint xmlProcessors =
+ registry.getExtensionPoint(StAXArtifactProcessorExtensionPoint.class);
+ StAXArtifactProcessor<Composite> compositeProcessor =
+ xmlProcessors.getProcessor(Composite.class);
+
+ return writeComposite(composite, registry, compositeProcessor);
+
+ //String compositeAsString = writeComposite(composite, registry, compositeProcessor);
+ //return "something";
+
+ //return Response.ok(compositeText, MediaType.APPLICATION_XML).build();
+
+ //return Response.ok().type(MediaType.APPLICATION_XML).entity(compositeAsString).build();
+ }
+
+
+ private String writeComposite(Composite composite, ExtensionPointRegistry registry, StAXArtifactProcessor<Composite> compositeProcessor){
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ XMLOutputFactory outputFactory =
+ registry.getExtensionPoint(FactoryExtensionPoint.class)
+ .getFactory(XMLOutputFactory.class);
+
+ try {
+ compositeProcessor.write(composite, outputFactory.createXMLStreamWriter(bos), new ProcessorContext(registry));
+ } catch(Exception ex) {
+ return ex.toString();
+ }
+
+ String result = bos.toString();
+
+ // write out and nested composites
+ for (Component component : composite.getComponents()) {
+ if (component.getImplementation() instanceof Composite) {
+ result += "\n<!-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -->\n" +
+ writeComposite((Composite)component.getImplementation(), registry,
+ compositeProcessor);
+ }
+ }
+ return result;
+ }
+}
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/main/resources/META-INF/services/org.apache.tuscany.sca.node.extensibility.NodeActivator b/sca-java-2.x/trunk/modules/node-manager/src/main/resources/META-INF/services/org.apache.tuscany.sca.node.extensibility.NodeActivator
new file mode 100644
index 0000000000..76ecb610ff
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/main/resources/META-INF/services/org.apache.tuscany.sca.node.extensibility.NodeActivator
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# Implementation class for the ModuleActivator
+org.apache.tuscany.sca.node.manager.impl.DomainCompositeResourceImpl;ranking=100
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/java/org/apache/tuscany/sca/node/manager/DomainCompositeResourceTestCase.java b/sca-java-2.x/trunk/modules/node-manager/src/test/java/org/apache/tuscany/sca/node/manager/DomainCompositeResourceTestCase.java
new file mode 100644
index 0000000000..3bebf97707
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/java/org/apache/tuscany/sca/node/manager/DomainCompositeResourceTestCase.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tuscany.sca.node.manager;
+
+import java.net.Socket;
+
+import org.apache.tuscany.sca.node.Contribution;
+import org.apache.tuscany.sca.node.ContributionLocationHelper;
+import org.apache.tuscany.sca.node.Node;
+import org.apache.tuscany.sca.node.NodeFactory;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.meterware.httpunit.GetMethodWebRequest;
+import com.meterware.httpunit.WebConversation;
+import com.meterware.httpunit.WebRequest;
+import com.meterware.httpunit.WebResponse;
+
+public class DomainCompositeResourceTestCase {
+ private static final String SERVICE_URL = "http://localhost:8080/domain";
+
+ private static Node node;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ try {
+ String contribution = ContributionLocationHelper.getContributionLocation(DomainCompositeResourceTestCase.class);
+ node = NodeFactory.newInstance().createNode("node-manager-test.composite", new Contribution("node-manager", contribution));
+ node.start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @AfterClass
+ public static void destroy() throws Exception {
+ if (node != null) {
+ node.stop();
+ }
+ }
+
+ @Test
+ public void testPing() throws Exception {
+ new Socket("127.0.0.1", 8080);
+ System.in.read();
+ }
+
+ @Test
+ public void testDefaultDomainCompositeResource() throws Exception {
+ String url = SERVICE_URL + "/default";
+ WebConversation wc = new WebConversation();
+ WebRequest request = new GetMethodWebRequest(url);
+ WebResponse response = wc.getResource(request);
+
+ Assert.assertEquals(200, response.getResponseCode());
+ System.out.println(">>>" + response.getText());
+ }
+
+}
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/node-manager-test.composite b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/node-manager-test.composite
new file mode 100644
index 0000000000..6f107a392d
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/node-manager-test.composite
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912"
+ xmlns:tuscany="http://tuscany.apache.org/xmlns/sca/1.1"
+ targetNamespace="http://node-manager"
+ name="NodeManager">
+
+ <component name="NodeManagerUI">
+ <tuscany:implementation.widget location="ui/index.html" />
+ <service name="Widget">
+ <tuscany:binding.rest uri="/manager/ui" />
+ </service>
+ </component>
+
+ <component name="NodeManagerComponent">
+ <implementation.java class="org.apache.tuscany.sca.node.manager.impl.DomainCompositeResourceImpl"/>
+ <service name="DomainCompositeResource">
+ <tuscany:binding.rest uri="/domain">
+ <tuscany:operationSelector.jaxrs />
+ </tuscany:binding.rest>
+ </service>
+ </component>
+</composite>
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/atomutil.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/atomutil.js
new file mode 100644
index 0000000000..182b698596
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/atomutil.js
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * ATOM data conversion functions.
+ */
+var atom = {};
+
+/**
+ * Convert a list of elements to a list of values representing an ATOM entry.
+ */
+atom.entryElementValues = function(e) {
+ var lt = filter(selector(mklist(element, "'title")), e);
+ var t = isNil(lt)? '' : elementValue(car(lt));
+ var li = filter(selector(mklist(element, "'id")), e);
+ var i = isNil(li)? '' : elementValue(car(li));
+ var lc = filter(selector(mklist(element, "'content")), e);
+ return append(mklist(element, "'entry", mklist(element, "'title", t), mklist(element, "'id", i)),
+ isNil(lc)? mklist() : mklist(mklist(element, "'content", elementValue(car(lc)))))
+};
+
+/**
+ * Convert a list of elements to a list of values representing ATOM entries.
+ */
+atom.entriesElementValues = function(e) {
+ if (isNil(e))
+ return e;
+ return cons(atom.entryElementValues(car(e)), atom.entriesElementValues(cdr(e)));
+};
+
+/**
+ * Return true if a list of strings represents an ATOM entry.
+ */
+atom.isATOMEntry = function(l) {
+ if (!isXML(l))
+ return false;
+ return car(l).match('<entry') != null && car(l).match('<feed') == null && car(l).match('="http://www.w3.org/2005/Atom"') != null;
+};
+
+/**
+ * Convert a DOM Document to a list of values representing an ATOM entry.
+ */
+atom.readATOMEntryDocument = function(doc) {
+ var e = readXMLDocument(doc);
+ if (isNil(e))
+ return mklist();
+ return mklist(atom.entryElementValues(car(e)));
+};
+
+/**
+ * Convert a list of strings to a list of values representing an ATOM entry.
+ */
+atom.readATOMEntry = function(l) {
+ return atom.readATOMEntryDocument(parseXML(l));
+};
+
+/**
+ * Return true if a list of strings represents an ATOM feed.
+ */
+atom.isATOMFeed = function(l) {
+ if (!isXML(l))
+ return false;
+ return car(l).match('<feed') != null && car(l).match('="http://www.w3.org/2005/Atom"') != null;
+};
+
+/**
+ * Convert a DOM document to a list of values representing an ATOM feed.
+ */
+atom.readATOMFeedDocument = function(doc) {
+ var f = readXMLDocument(doc);
+ if (isNil(f))
+ return mklist();
+ var t = filter(selector(mklist(element, "'title")), car(f));
+ var i = filter(selector(mklist(element, "'id")), car(f));
+ var e = filter(selector(mklist(element, "'entry")), car(f));
+ return mklist(append(
+ mklist(element, "'feed", mklist(element, "'title", elementValue(car(t))), mklist(element, "'id", elementValue(car(i)))),
+ atom.entriesElementValues(e)));
+};
+
+/**
+ * Convert a list of strings to a list of values representing an ATOM feed.
+ */
+atom.readATOMFeed = function(l) {
+ return atom.readATOMFeedDocument(parseXML(l));
+};
+
+/**
+ * Convert a list of values representy an ATOM entry to a list of elements.
+ */
+atom.entryElement = function(l) {
+ var title = elementValue(namedElementChild("'title", l));
+ var id = elementValue(namedElementChild("'id", l));
+ var content = namedElementChild("'content", l);
+ var text = isNil(content)? false : elementHasValue(content);
+ return append(append(
+ mklist(element, "'entry", mklist(attribute, "'xmlns", "http://www.w3.org/2005/Atom"),
+ mklist(element, "'title", mklist(attribute, "'type", "text"), title), mklist(element, "'id", id)),
+ isNil(content)? mklist() :
+ append(mklist(element, "'content", mklist(attribute, "'type", text? "text" : "application/xml")), text? mklist(elementValue(content)) : elementChildren(content))),
+ mklist(element, "'link", mklist(attribute, "'href", id)));
+};
+
+/**
+ * Convert a list of values representing ATOM entries to a list of elements.
+ */
+atom.entriesElements = function(l) {
+ if (isNil(l))
+ return l;
+ return cons(atom.entryElement(car(l)), atom.entriesElements(cdr(l)));
+};
+
+/**
+ * Convert a list of values representing an ATOM entry to an ATOM entry.
+ */
+atom.writeATOMEntry = function(ll) {
+ var l = isNil(ll)? ll : car(ll);
+ return writeXML(mklist(atom.entryElement(l)), true);
+};
+
+/**
+ * Convert a list of values representing an ATOM feed to an ATOM feed.
+ */
+atom.writeATOMFeed = function(ll) {
+ var l = isNil(ll)? ll : car(ll);
+ var lt = filter(selector(mklist(element, "'title")), l);
+ var t = isNil(lt)? '' : elementValue(car(lt));
+ var li = filter(selector(mklist(element, "'id")), l);
+ var i = isNil(li)? '' : elementValue(car(li));
+ var f = mklist(element, "'feed", mklist(attribute, "'xmlns", "http://www.w3.org/2005/Atom"),
+ mklist(element, "'title", mklist(attribute, "'type", "text"), car(l)),
+ mklist(element, "'id", cadr(l)));
+
+ // Write ATOM entries
+ var le = filter(selector(mklist(element, "'entry")), l);
+ if (isNil(le))
+ return writeXML(mklist(f), true);
+
+ // Write a single ATOM entry element with a list of values
+ if (!isNil(le) && !isNil(car(le)) && isList(car(caddr(car(le))))) {
+ var fe = append(f, atom.entriesElements(caddr(car(le))));
+ return writeXML(mklist(fe), true);
+ }
+
+ // Write separate ATOM entry elements
+ var fe = append(f, atom.entriesElements(le));
+ return writeXML(mklist(fe), true);
+};
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/component.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/component.js
new file mode 100644
index 0000000000..beef9357e5
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/component.js
@@ -0,0 +1,443 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * The JSON-RPC client code is based on Jan-Klaas' JavaScript
+ * o lait library (jsolait).
+ *
+ * $Id: jsonrpc.js,v 1.36.2.3 2006/03/08 15:09:37 mclark Exp $
+ *
+ * Copyright (c) 2003-2004 Jan-Klaas Kollhof
+ * Copyright (c) 2005 Michael Clark, Metaparadigm Pte Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ */
+
+/**
+ * Client component wiring API, supporting JSON and ATOM bindings.
+ */
+
+var JSONClient = {};
+
+/**
+ * Escape a character.
+ */
+JSONClient.escapeJSONChar = function(c) {
+ if(c == "\"" || c == "\\") return "\\" + c;
+ if (c == "\b") return "\\b";
+ if (c == "\f") return "\\f";
+ if (c == "\n") return "\\n";
+ if (c == "\r") return "\\r";
+ if (c == "\t") return "\\t";
+ var hex = c.charCodeAt(0).toString(16);
+ if(hex.length == 1) return "\\u000" + hex;
+ if(hex.length == 2) return "\\u00" + hex;
+ if(hex.length == 3) return "\\u0" + hex;
+ return "\\u" + hex;
+};
+
+/**
+ * Encode a string into JSON format.
+ */
+JSONClient.escapeJSONString = function(s) {
+ // The following should suffice but Safari's regex is broken (doesn't support callback substitutions)
+ // return "\"" + s.replace(/([^\u0020-\u007f]|[\\\"])/g, JSONClient.escapeJSONChar) + "\"";
+
+ // Rather inefficient way to do it
+ var parts = s.split("");
+ for(var i = 0; i < parts.length; i++) {
+ var c = parts[i];
+ if(c == '"' || c == '\\' || c.charCodeAt(0) < 32 || c.charCodeAt(0) >= 128)
+ parts[i] = JSONClient.escapeJSONChar(parts[i]);
+ }
+ return "\"" + parts.join("") + "\"";
+};
+
+/**
+ * Marshall objects to JSON format.
+ */
+JSONClient.toJSON = function(o) {
+ if(o == null)
+ return "null";
+ if(o.constructor == String)
+ return JSONClient.escapeJSONString(o);
+ if(o.constructor == Number)
+ return o.toString();
+ if(o.constructor == Boolean)
+ return o.toString();
+ if(o.constructor == Date)
+ return '{javaClass: "java.util.Date", time: ' + o.valueOf() +'}';
+ if(o.constructor == Array) {
+ var v = [];
+ for(var i = 0; i < o.length; i++)
+ v.push(JSONClient.toJSON(o[i]));
+ return "[" + v.join(", ") + "]";
+ }
+ var v = [];
+ for(attr in o) {
+ if(o[attr] == null)
+ v.push("\"" + attr + "\": null");
+ else if(typeof o[attr] == "function")
+ ; // Skip
+ else
+ v.push(JSONClient.escapeJSONString(attr) + ": " + JSONClient.toJSON(o[attr]));
+ }
+ return "{" + v.join(", ") + "}";
+};
+
+/**
+ * Construct an HTTPBindingClient.
+ */
+function HTTPBindingClient(name, uri) {
+ this.name = name;
+ this.uri = uri;
+ this.apply = this.createApplyMethod();
+}
+
+/**
+ * JSON-RPC request counter.
+ */
+HTTPBindingClient.jsonrpcID = 1;
+
+/**
+ * HTTPBindingClient implementation
+ */
+
+/**
+ * Generate client proxy apply method.
+ */
+HTTPBindingClient.prototype.createApplyMethod = function() {
+ var fn = function() {
+ var methodName = arguments[0];
+ var args = [];
+ for(var i = 1; i < arguments.length; i++)
+ args.push(arguments[i]);
+
+ var cb = null;
+ if (typeof args[args.length - 1] == "function")
+ cb = args.pop();
+
+ var req = HTTPBindingClient.makeJSONRequest(methodName, args, cb);
+ return fn.client.jsonApply(req);
+ };
+ fn.client = this;
+ return fn;
+};
+
+/**
+ * Make a JSON-RPC request.
+ */
+HTTPBindingClient.makeJSONRequest = function(methodName, args, cb) {
+ var req = {};
+ req.id = HTTPBindingClient.jsonrpcID++;
+ if (cb)
+ req.cb = cb;
+ var obj = {};
+ obj.id = req.id;
+ obj.method = methodName;
+ obj.params = args;
+ req.data = JSONClient.toJSON(obj);
+ return req;
+};
+
+/**
+ * Return the JSON result from an XMLHttpRequest.
+ */
+HTTPBindingClient.jsonResult = function(http) {
+ // Get the charset
+ function httpCharset(http) {
+ try {
+ var contentType = http.getResponseHeader("Content-type");
+ var parts = contentType.split(/\s*;\s*/);
+ for (var i = 0; i < parts.length; i++) {
+ if (parts[i].substring(0, 8) == "charset=")
+ return parts[i].substring(8, parts[i].length);
+ }
+ } catch (e) {}
+ return "UTF-8";
+ }
+ if(!HTTPBindingClient.charset)
+ HTTPBindingClient.charset = httpCharset(http);
+
+ // Unmarshall the JSON response
+ var obj;
+ eval("obj = " + http.responseText);
+ if(obj.error)
+ throw new HTTPBindingClient.Exception(obj.error.code, obj.error.msg);
+ var res = obj.result;
+ return res;
+};
+
+/**
+ * Apply a function remotely using JSON-RPC.
+ */
+HTTPBindingClient.prototype.jsonApply = function(req) {
+ // Connect to the service
+ var http = HTTPBindingClient.getHTTPRequest();
+ var hascb = req.cb? true : false;
+ http.open("POST", this.uri, hascb);
+ http.setRequestHeader("Content-type", "application/json-rpc");
+
+ // Construct call back if we have one
+ if(hascb) {
+ http.onreadystatechange = function() {
+ if(http.readyState == 4) {
+ // Pass the result or exception
+ if(http.status == 200) {
+ var res = null;
+ try {
+ res = HTTPBindingClient.jsonResult(http);
+ } catch(e) {
+ req.cb(null, e);
+ }
+ req.cb(res);
+ } else
+ req.cb(null, HTTPBindingClient.Exception(http.status, http.statusText));
+ }
+ };
+
+ // Send the request
+ http.send(req.data);
+ return req.id;
+ }
+
+ // Send the request and return the result or exception
+ http.send(req.data);
+ if (http.status == 200)
+ return HTTPBindingClient.jsonResult(http);
+ throw new HTTPBindingClient.Exception(http.status, http.statusText);
+};
+
+/**
+ * REST ATOMPub GET method.
+ */
+HTTPBindingClient.prototype.get = function(id, cb) {
+ // Connect to the service
+ var http = HTTPBindingClient.getHTTPRequest();
+ var hascb = cb? true : false;
+ http.open("GET", this.uri + '/' + id, hascb);
+
+ // Construct call back if we have one
+ if (hascb) {
+ http.onreadystatechange = function() {
+ if (http.readyState == 4) {
+ // Pass the result or exception
+ if (http.status == 200)
+ cb(http.responseText);
+ else
+ cb(null, new HTTPBindingClient.Exception(http.status, http.statusText));
+ }
+ };
+
+ // Send the request
+ http.send(null);
+ return true;
+ }
+
+ // Send the request and return the result or exception
+ http.send(null);
+ if (http.status == 200)
+ return http.responseText;
+ throw new HTTPBindingClient.Exception(http.status, http.statusText);
+};
+
+/**
+ * REST ATOMPub POST method.
+ */
+HTTPBindingClient.prototype.post = function (entry, cb) {
+ // Connect to the service
+ var http = HTTPBindingClient.getHTTPRequest();
+ var hascb = cb? true : false;
+ http.open("POST", this.uri, hascb);
+ http.setRequestHeader("Content-Type", "application/atom+xml");
+
+ // Construct call back if we have one
+ if (hascb) {
+ http.onreadystatechange = function() {
+ // Pass the result or exception
+ if (http.readyState == 4) {
+ if (http.status == 201)
+ cb(http.responseText);
+ else
+ cb(null, new HTTPBindingClient.Exception(http.status, http.statusText));
+ }
+ };
+ // Send the request
+ http.send(entry);
+ return true;
+ }
+
+ // Send the request and return the result or exception
+ http.send(entry);
+ if (http.status == 201)
+ return http.responseText;
+ throw new HTTPBindingClient.Exception(http.status, http.statusText);
+};
+
+/**
+ * REST ATOMPub PUT method.
+ */
+HTTPBindingClient.prototype.put = function (id, entry, cb) {
+ // Connect to the service
+ var http = HTTPBindingClient.getHTTPRequest();
+ var hascb = cb? true : false;
+ http.open("PUT", this.uri + '/' + id, hascb);
+ http.setRequestHeader("Content-Type", "application/atom+xml");
+
+ // Construct call back if we have one
+ if (hascb) {
+ http.onreadystatechange = function() {
+ if (http.readyState == 4) {
+ // Pass any exception
+ if (http.status == 200)
+ cb();
+ else
+ cb(new HTTPBindingClient.Exception(http.status, http.statusText));
+ }
+ };
+ // Send the request
+ http.send(entry);
+ return true;
+ }
+
+ // Send the request and return any exception
+ http.send(entry);
+ if (http.status == 200)
+ return true;
+ throw new HTTPBindingClient.Exception(http.status, http.statusText);
+};
+
+/**
+ * REST ATOMPub DELETE method.
+ */
+HTTPBindingClient.prototype.del = function (id, cb) {
+ // Connect to the service
+ var http = HTTPBindingClient.getHTTPRequest();
+ var hascb = cb? true : false;
+ http.open("DELETE", this.uri + '/' + id, hascb);
+
+ // Construct call back if we have one
+ if (cb) {
+ http.onreadystatechange = function() {
+ if (http.readyState == 4) {
+ // Pass any exception
+ if (http.status == 200)
+ cb();
+ else
+ cb(new HTTPBindingClient.Exception(http.status, http.statusText));
+ }
+ };
+ // Send the request
+ http.send(null);
+ return true;
+ }
+
+ // Send the request and return any exception
+ http.send(null);
+ if (http.status == 200)
+ return true;
+ throw new HTTPBindingClient.Exception(http.status, http.statusText);
+};
+
+/**
+ * HTTPBindingClient exceptions.
+ */
+HTTPBindingClient.Exception = function(code, message) {
+ this.name = "HTTPBindingClientException";
+ this.code = code;
+ this.message = message;
+};
+
+HTTPBindingClient.Exception.prototype = new Error();
+
+HTTPBindingClient.Exception.prototype.toString = function() {
+ return this.name + ": " + this.message;
+};
+
+/**
+ * XMLHttpRequest wrapper.
+ */
+HTTPBindingClient.msxmlNames = [ "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];
+
+HTTPBindingClient.getHTTPRequest = function() {
+ if (HTTPBindingClient.httpFactory)
+ return HTTPBindingClient.httpFactory();
+
+ // Mozilla XMLHttpRequest
+ try {
+ HTTPBindingClient.httpFactory = function() {
+ return new XMLHttpRequest();
+ };
+ return HTTPBindingClient.httpFactory();
+ } catch(e) {}
+
+ // Microsoft MSXML ActiveX
+ for (var i = 0; i < HTTPBindingClient.msxmlNames.length; i++) {
+ try {
+ HTTPBindingClient.httpFactory = function() {
+ return new ActiveXObject(HTTPBindingClient.msxmlNames[i]);
+ };
+ return HTTPBindingClient.httpFactory();
+ } catch (e) {}
+ }
+
+ // Can't create XMLHttpRequest
+ HTTPBindingClient.httpFactory = null;
+ throw new HTTPBindingClient.Exception(0, "Can't create XMLHttpRequest object");
+};
+
+/**
+ * Public API.
+ */
+
+var sca = {};
+
+/**
+ * Return a component proxy.
+ */
+sca.component = function(name) {
+ return new HTTPBindingClient(name, '/components/' + name);
+};
+
+/**
+ * Return a reference proxy.
+ */
+sca.reference = function(comp, rname) {
+ return new HTTPBindingClient(comp.name + '/' + rname, "/references/" + comp.name + "/" + rname);
+};
+
+/**
+ * Add proxy functions to a reference proxy.
+ */
+sca.defun = function(ref) {
+ function defapply(name) {
+ return function() {
+ var args = new Array();
+ args[0] = name;
+ for (i = 0, n = arguments.length; i < n; i++)
+ args[i + 1] = arguments[i];
+ return this.apply.apply(this, args);
+ };
+ }
+
+ for (f = 1; f < arguments.length; f++) {
+ var fn = arguments[f];
+ ref[fn]= defapply(fn);
+ }
+ return ref;
+};
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/elemutil.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/elemutil.js
new file mode 100644
index 0000000000..1c006e1e7c
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/elemutil.js
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Functions to help represent data as lists of elements and attributes.
+ */
+
+var element = "'element"
+var attribute = "'attribute"
+var atsign = "'@"
+
+/**
+ * Return true if a value is an element.
+ */
+function isElement(v) {
+ if (!isList(v) || isNil(v) || car(v) != element)
+ return false;
+ return true;
+}
+
+/**
+ * Return true if a value is an attribute.
+ */
+function isAttribute(v) {
+ if (!isList(v) || isNil(v) || car(v) != attribute)
+ return false;
+ return true;
+}
+
+/**
+ * Return the name of an attribute.
+ */
+function attributeName(l) {
+ return cadr(l);
+}
+
+/**
+ * Return the value of an attribute.
+ */
+function attributeValue(l) {
+ return caddr(l);
+}
+
+/**
+ * Return the name of an element.
+ */
+function elementName(l) {
+ return cadr(l);
+}
+
+/**
+ * Return true if an element has children.
+ */
+function elementHasChildren(l) {
+ return !isNil(cddr(l));
+}
+
+/**
+ * Return the children of an element.
+ */
+function elementChildren(l) {
+ return cddr(l);
+}
+
+
+/**
+ * Return true if an element has a value.
+ */
+function elementHasValue(l) {
+ r = reverse(l);
+ if (isSymbol(car(r)))
+ return false;
+ if (isList(car(r)) && !isNil(car(r)) && isSymbol(car(car(r))))
+ return false;
+ return true;
+}
+
+/**
+ * Return the value of an element.
+ */
+function elementValue(l) {
+ return car(reverse(l));
+}
+
+/**
+ * Convert an element to a value.
+ */
+function elementToValueIsList(v) {
+ if (!isList(v))
+ return false;
+ return isNil(v) || !isSymbol(car(v));
+}
+
+function elementToValue(t) {
+ if (isTaggedList(t, attribute))
+ return mklist(atsign + attributeName(t).substring(1), attributeValue(t));
+ if (isTaggedList(t, element)) {
+ if (elementHasValue(t)) {
+ if (!elementToValueIsList(elementValue(t)))
+ return mklist(elementName(t), elementValue(t));
+ return cons(elementName(t), mklist(elementsToValues(elementValue(t))));
+ }
+ return cons(elementName(t), elementsToValues(elementChildren(t)));
+ }
+ if (!isList(t))
+ return t;
+ return elementsToValues(t);
+}
+
+/**
+ * Convert a list of elements to a list of values.
+ */
+function elementToValueIsSymbol(v) {
+ if (!isList(v))
+ return false;
+ if (isNil(v))
+ return false;
+ if (!isSymbol(car(v)))
+ return false;
+ return true;
+}
+
+function elementToValueGroupValues(v, l) {
+ if (isNil(l) || !elementToValueIsSymbol(v) || !elementToValueIsSymbol(car(l)))
+ return cons(v, l);
+ if (car(car(l)) != car(v))
+ return cons(v, l);
+ if (!elementToValueIsList(cadr(car(l)))) {
+ var g = mklist(car(v), mklist(cdr(v), cdr(car(l))));
+ return elementToValueGroupValues(g, cdr(l));
+ }
+ var g = mklist(car(v), cons(cdr(v), cadr(car(l))));
+ return elementToValueGroupValues(g, cdr(l));
+}
+
+function elementsToValues(e) {
+ if (isNil(e))
+ return e;
+ return elementToValueGroupValues(elementToValue(car(e)), elementsToValues(cdr(e)));
+}
+
+/**
+ * Convert a value to an element.
+ */
+function valueToElement(t) {
+ if (isList(t) && !isNil(t) && isSymbol(car(t))) {
+ var n = car(t);
+ var v = isNil(cdr(t))? mklist() : cadr(t);
+ if (!isList(v)) {
+ if (n.substring(0, 2) == atsign)
+ return mklist(attribute, "'" + n.substring(2), v);
+ return mklist(element, n, v);
+ }
+ if (isNil(v) || !isSymbol(car(v)))
+ return cons(element, cons(n, mklist(valuesToElements(v))));
+ return cons(element, cons(n, valuesToElements(cdr(t))));
+ }
+ if (!isList(t))
+ return t;
+ return valuesToElements(t);
+}
+
+/**
+ * Convert a list of values to a list of elements.
+ */
+function valuesToElements(l) {
+ if (isNil(l))
+ return l;
+ return cons(valueToElement(car(l)), valuesToElements(cdr(l)));
+}
+
+/**
+ * Return a selector lambda function which can be used to filter elements.
+ */
+function selector(s) {
+ function evalSelect(s, v) {
+ if (isNil(s))
+ return true;
+ if (isNil(v))
+ return false;
+ if (car(s) != car(v))
+ return false;
+ return evalSelect(cdr(s), cdr(v));
+ }
+
+ return function(v) { return evalSelect(s, v); };
+}
+
+/**
+ * Return the attribute with the given name.
+ */
+function namedAttribute(name, l) {
+ return memo(l, name, function() {
+ var f = filter(function(v) { return isAttribute(v) && attributeName(v) == name; }, l);
+ if (isNil(f))
+ return null;
+ return car(f);
+ });
+}
+
+/**
+ * Return the value of the attribute with the given name.
+ */
+function namedAttributeValue(name, l) {
+ var a = namedAttribute(name, l);
+ if (a == null)
+ return null
+ return attributeValue(a);
+}
+
+/**
+ * Return child elements with the given name.
+ */
+function namedElementChildren(name, l) {
+ return memo(l, name, function() {
+ return filter(function(v) { return isElement(v) && elementName(v) == name; }, l);
+ });
+}
+
+/**
+ * Return the child element with the given name.
+ */
+function namedElementChild(name, l) {
+ var f = namedElementChildren(name, l);
+ if (isNil(f))
+ return null;
+ return car(f);
+}
+
+/**
+ * Side effect functions. Use with moderation.
+ */
+
+/**
+ * Set the contents of an element.
+ */
+function setElement(l, e) {
+ setlist(l, e);
+ l.memo = {};
+}
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/graph.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/graph.js
new file mode 100644
index 0000000000..3535dce0df
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/graph.js
@@ -0,0 +1,1913 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * SVG and VML composite rendering functions.
+ */
+
+var graph = {};
+
+/**
+ * Basic colors
+ */
+graph.colors = {};
+graph.colors.black = '#000000';
+graph.colors.blue = '#0000ff';
+graph.colors.cyan = '#00ffff';
+graph.colors.gray = '#808080'
+graph.colors.lightgray = '#dcdcdc'
+graph.colors.green = '#00ff00';
+graph.colors.magenta = '#ff00ff';
+graph.colors.orange = '#ffa500';
+graph.colors.pink = '#ffc0cb';
+graph.colors.purple = '#800080';
+graph.colors.red = '#ff0000';
+graph.colors.white = '#ffffff';
+graph.colors.yellow = '#ffff00';
+
+graph.colors.orange1 = '#ffbb00';
+graph.colors.green1 = '#96d333';
+graph.colors.blue1 = '#00c3c9';
+graph.colors.red1 = '#d03f41';
+graph.colors.yellow1 = '#fcee21';
+graph.colors.magenta1 = '#c0688a';
+
+/**
+ * Default positions and sizes.
+ */
+var palcx = 250;
+var trashcx = 230;
+var proxcx = 20;
+var proxcy = 20;
+var buttoncx = 70;
+var buttoncy = 30;
+var curvsz = 6;
+var tabsz = 2;
+
+/**
+ * Base path class.
+ */
+graph.BasePath = function() {
+ this.path = '';
+ this.x = 0;
+ this.y = 0;
+
+ this.pos = function(x, y) {
+ this.x = x;
+ this.y = y;
+ return this;
+ };
+
+ this.xpos = function() {
+ return this.x;
+ };
+
+ this.ypos = function() {
+ return this.y;
+ };
+
+ this.rmove = function(x, y) {
+ return this.move(this.x + x, this.y + y);
+ };
+
+ this.rline = function(x, y) {
+ return this.line(this.x + x, this.y + y);
+ };
+
+ this.rcurve = function(x1, y1, x, y) {
+ return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y);
+ };
+
+ this.str = function() {
+ return this.path;
+ };
+};
+
+/**
+ * Rendering functions that work both with VML and SVG.
+ */
+
+/**
+ * VML rendering.
+ */
+if (ui.isIE()) {
+
+ graph.vmlns='urn:schemas-microsoft-com:vml';
+ document.write('<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />');
+
+ /**
+ * Make a VML graph.
+ */
+ graph.mkgraph = function(pos, cname, pvalue) {
+
+ // Create div element to host the graph
+ var div = document.createElement('div');
+ div.id = 'vmldiv';
+ div.style.position = 'absolute';
+ div.style.left = pos.xpos();
+ div.style.top = pos.ypos();
+ document.body.appendChild(div);
+
+ // Create a VML group
+ var vmlg = document.createElement('v:group');
+ vmlg.style.width = 5000;
+ vmlg.style.height = 5000;
+ vmlg.coordsize = '5000,5000';
+ div.appendChild(vmlg);
+
+ // Track element dragging and selection
+ graph.dragging = null;
+ graph.selected = null;
+
+ /**
+ * Find the first draggable element in a hierarchy of elements.
+ */
+ function draggable(n) {
+ if (n == vmlg)
+ return null;
+ if (n.nodeName == 'group' && n.id != '')
+ return n;
+ return draggable(n.parentNode);
+ }
+
+ /**
+ * Handle a mousedown event.
+ */
+ vmlg.onmousedown = function() {
+ window.event.returnValue = false;
+
+ // Find draggable element
+ graph.dragging = draggable(window.event.srcElement);
+ graph.selected = graph.dragging;
+ if (graph.dragging == null) {
+
+ // Reset current selection
+ cname.value = '';
+ pvalue.value = '';
+
+ // Trigger component select event
+ vmlg.oncompselect('');
+ return false;
+ }
+
+ // Clone component from the palette
+ var compos = scdl.composite(vmlg.compos);
+ if (graph.dragging.id.substring(0, 8) == 'palette:') {
+ graph.dragging = graph.clonepalette(graph.dragging, compos);
+ graph.selected = graph.dragging;
+ }
+
+ // Cut wire to component
+ if (graph.dragging.parentNode != vmlg)
+ setElement(compos, graph.cutwire(graph.dragging, compos, vmlg));
+
+ // Bring component to the top
+ graph.bringtotop(graph.dragging, vmlg);
+
+ // Remember mouse position
+ graph.dragX = window.event.clientX;
+ graph.dragY = window.event.clientY;
+ vmlg.setCapture();
+
+ // Update the component name and property value fields
+ cname.value = graph.selected.id;
+ pvalue.value = graph.property(graph.selected.comp);
+
+ // Trigger component select event
+ vmlg.oncompselect(vmlg.appname, graph.selected.id);
+ return false;
+ };
+
+ /**
+ * Handle a mouseup event.
+ */
+ vmlg.onmouseup = function() {
+ if (graph.dragging == null)
+ return false;
+
+ if (graph.dragging.parentNode == vmlg && graph.dragging.id.substring(0, 8) != 'palette:') {
+ var gpos = graph.relpos(graph.dragging);
+ if (gpos.xpos() >= trashcx) {
+
+ // If component close enough to editing area, move it there
+ if (gpos.xpos() < palcx)
+ graph.move(graph.dragging, graph.mkpath().move(palcx, gpos.ypos()));
+
+ // Add new dragged component to the composite
+ if (isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(vmlg.compos);
+ setElement(compos, graph.addcomp(graph.dragging.comp, compos));
+ graph.dragging.compos = vmlg.compos;
+ }
+
+ // Update component position
+ setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.dragging, vmlg)));
+
+ // Wire component to neighboring reference
+ if (!isNil(graph.dragging.svcpos)) {
+ var compos = scdl.composite(vmlg.compos);
+ setElement(compos, grah.clonerefs(graph.wire(graph.dragging, compos, vmlg)));
+ }
+
+ } else {
+
+ // Discard component dragged out of composite
+ vmlg.removeChild(graph.dragging);
+ if (!isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(vmlg.compos);
+ setElement(compos, graph.clonerefs(graph.gcollect(graph.removecomp(graph.dragging.comp, compos))));
+ }
+
+ // Reset current selection
+ graph.selected = null;
+ cname.value = '';
+ pvalue.value = '';
+
+ // Trigger component select event
+ vmlg.oncompselect('');
+ }
+
+ // Trigger composite change event
+ vmlg.oncomposchange();
+ }
+
+ // Forget current dragged component
+ graph.dragging = null;
+ vmlg.releaseCapture();
+
+ // Refresh the composite
+ graph.refresh(vmlg);
+ return false;
+ };
+
+ /**
+ * Handle a mousemove event.
+ */
+ vmlg.onmousemove = function() {
+ if (graph.dragging == null)
+ return false;
+
+ // Calculate new position of dragged element
+ var gpos = graph.relpos(graph.dragging);
+ var newX = gpos.xpos() + (window.event.clientX - graph.dragX);
+ var newY = gpos.ypos() + (window.event.clientY - graph.dragY);
+ if (newX >= 0)
+ graph.dragX = window.event.clientX;
+ else
+ newX = 0;
+ if (newY >= 0)
+ graph.dragY = window.event.clientY;
+ else
+ newY = 0;
+
+ // Move the dragged element
+ graph.move(graph.dragging, graph.mkpath().move(newX, newY));
+
+ return false;
+ };
+
+ /**
+ * Handle field on change events.
+ */
+ cname.onchange = function() {
+ if (graph.selected == null)
+ return false;
+
+ // Change component name and refactor references to it
+ var compos = scdl.composite(vmlg.compos);
+ cname.value = graph.ucid(cname.value, compos);
+ graph.selected.id = cname.value;
+ setElement(compos, graph.renamecomp(graph.selected.comp, compos, cname.value));
+
+ // Trigger component select event
+ vmlg.oncompselect(vmlg.appname, graph.selected.id);
+
+ // Refresh the composite
+ graph.refresh(vmlg);
+
+ // Trigger composite change event
+ vmlg.oncomposchange();
+ return false;
+ };
+
+ pvalue.onchange = function() {
+ if (graph.selected == null)
+ return false;
+
+ // Change the component property value
+ graph.setproperty(graph.selected.comp, pvalue.value);
+ pvalue.value = graph.property(graph.selected.comp);
+
+ // Refresh the composite
+ graph.refresh(vmlg);
+
+ // Trigger composite change event
+ vmlg.oncomposchange();
+ return false;
+ };
+
+ // Create hidden spans to help compute the width of
+ // component, reference and property titles
+ graph.comptitlewidthdiv = document.createElement('span');
+ graph.comptitlewidthdiv.style.visibility = 'hidden'
+ div.appendChild(graph.comptitlewidthdiv);
+
+ graph.reftitlewidthdiv = document.createElement('span');
+ graph.reftitlewidthdiv.style.visibility = 'hidden'
+ div.appendChild(graph.reftitlewidthdiv);
+
+ graph.proptitlewidthdiv = document.createElement('span');
+ graph.proptitlewidthdiv.style.visibility = 'hidden'
+ graph.proptitlewidthdiv.style.fontWeight = 'bold'
+ div.appendChild(graph.proptitlewidthdiv);
+
+ return vmlg;
+ };
+
+ /**
+ * Make a shape path.
+ */
+ graph.mkpath = function() {
+ function Path() {
+ this.BasePath = graph.BasePath;
+ this.BasePath();
+
+ this.clone = function() {
+ return graph.mkpath().pos(this.xpos(), this.ypos());
+ };
+
+ this.move = function(x, y) {
+ this.path += 'M ' + x + ',' + y + ' ';
+ return this.pos(x, y);
+ };
+
+ this.line = function(x, y) {
+ this.path += 'L ' + x + ',' + y + ' ';
+ return this.pos(x, y);
+ };
+
+ this.curve = function(x1, y1, x, y) {
+ this.path += 'QB ' + x1 + ',' + y1 + ',' + x + ',' + y + ' ';
+ return this.pos(x, y);
+ };
+
+ this.end = function() {
+ this.path += 'X E';
+ return this;
+ };
+ }
+
+ return new Path();
+ };
+
+ /**
+ * Return an element representing a title.
+ */
+ graph.mktitle = function(t, bold, pos) {
+ var title = document.createElement('v:textbox');
+ title.style.position = 'absolute';
+ title.style.left = pos.xpos() + 2;
+ title.style.top = pos.ypos();
+ title.inset = '' + 6 + 'px ' + pos.ypos() + 'px 0px 0px';
+ if (bold)
+ title.style.fontWeight = 'bold';
+ var tnode = document.createTextNode(t);
+ title.appendChild(tnode);
+ return title;
+ };
+
+ /**
+ * Return an element representing the title of a component.
+ */
+ graph.comptitle = function(comp) {
+ var tsvcs = graph.tsvcs(comp);
+ var lsvcs = graph.lsvcs(comp);
+ var pos = graph.mkpath().move(isNil(lsvcs)? tabsz : (tabsz * 5), isNil(tsvcs)? tabsz : (tabsz * 5));
+ return graph.mktitle(graph.title(comp), false, pos);
+ };
+
+ /**
+ * Return the width of the title of a component.
+ */
+ graph.comptitlewidth = function(comp) {
+ var t = graph.title(comp);
+ graph.comptitlewidthdiv.innerHTML = t;
+ var twidth = graph.comptitlewidthdiv.offsetWidth + 4;
+ graph.comptitlewidthdiv.innerHTML = '';
+ return twidth;
+ };
+
+ /**
+ * Return an element representing the value of a property.
+ */
+ graph.proptitle = function(comp) {
+ var tsvcs = graph.tsvcs(comp);
+ var lsvcs = graph.lsvcs(comp);
+ var pos = graph.mkpath().move(isNil(lsvcs)? tabsz : (tabsz * 5), isNil(tsvcs)? 15 + tabsz : 15 + (tabsz * 5));
+ return graph.mktitle(graph.property(comp), true, pos);
+ };
+
+ /**
+ * Return the width of the value of a property.
+ */
+ graph.proptitlewidth = function(comp) {
+ var t = graph.property(comp);
+ graph.proptitlewidthdiv.innerHTML = t;
+ var twidth = graph.proptitlewidthdiv.offsetWidth + 4;
+ graph.proptitlewidthdiv.innerHTML = '';
+ return twidth;
+ };
+
+ /**
+ * Return an element representing the title of a reference.
+ */
+ graph.reftitle = function(ref) {
+ return graph.mktitle(graph.title(ref), false, graph.mkpath().move(25,25));
+ };
+
+ /**
+ * Return the width of the title of a reference.
+ */
+ graph.reftitlewidth = function(ref) {
+ var t = graph.title(ref);
+ graph.reftitlewidthdiv.innerHTML = t;
+ var twidth = graph.reftitlewidthdiv.offsetWidth;
+ graph.reftitlewidthdiv.innerHTML = '';
+ return twidth;
+ };
+
+ /**
+ * Return a node representing a component.
+ */
+ graph.compnode = function(comp, cassoc, pos) {
+
+ // Make the component and property title elements
+ var title = graph.comptitle(comp);
+ var prop = graph.proptitle(comp);
+
+ // Compute the component shape path
+ var path = graph.comppath(comp, cassoc);
+ var d = path.str();
+
+ // Create the main component shape
+ var shape = document.createElement('v:shape');
+ shape.style.width = 5000;
+ shape.style.height = 5000;
+ shape.coordsize = '5000,5000';
+ shape.path = d;
+ shape.fillcolor = graph.color(comp);
+ shape.stroked = 'false';
+
+ // Create an overlay contour shape
+ var contour = document.createElement('v:shape');
+ contour.style.width = 5000;
+ contour.style.height = 5000;
+ contour.coordsize = '5000,5000';
+ contour.path = d;
+ contour.filled = 'false';
+ contour.strokecolor = graph.colors.gray;
+ contour.strokeweight = '1';
+ contour.style.left = 1;
+ contour.style.top = 1;
+ var stroke = document.createElement('v:stroke');
+ stroke.opacity = '20%';
+ contour.appendChild(stroke);
+
+ // Create a group and add the component and contour shapes to it
+ var g = document.createElement('v:group');
+ g.id = scdl.name(comp);
+ g.style.width = 5000;
+ g.style.height = 5000;
+ g.coordsize = '5000,5000';
+ g.style.left = pos.xpos();
+ g.style.top = pos.ypos();
+ g.appendChild(shape);
+ shape.appendChild(title);
+ shape.appendChild(prop);
+ g.appendChild(contour)
+
+ // Store the component and the positions of its services
+ // and references in the component shape
+ g.comp = comp;
+ g.refpos = reverse(path.refpos);
+ g.svcpos = reverse(path.svcpos);
+
+ return g;
+ };
+
+ /**
+ * Return a graphical group.
+ */
+ graph.mkgroup = function(pos) {
+ var g = document.createElement('v:group');
+ g.style.left = pos.xpos();
+ g.style.top = pos.ypos();
+ return g;
+ };
+
+ /**
+ * Return a node representing a button.
+ */
+ graph.mkbutton = function(t, pos) {
+
+ // Make the title element
+ var title = graph.mktitle(t, true, graph.mkpath().move(4,4));
+
+ // Compute the path of the button shape
+ var path = graph.buttonpath().str();
+
+ // Create the main button shape
+ var shape = document.createElement('v:shape');
+ shape.style.width = 5000;
+ shape.style.height = 5000;
+ shape.coordsize = '5000,5000';
+ shape.path = path;
+ shape.fillcolor = graph.colors.lightgray;
+ shape.stroked = 'false';
+
+ // Create an overlay contour shape
+ var contour = document.createElement('v:shape');
+ contour.style.width = 5000;
+ contour.style.height = 5000;
+ contour.coordsize = '5000,5000';
+ contour.path = path;
+ contour.filled = 'false';
+ contour.strokecolor = graph.colors.gray;
+ contour.strokeweight = '1';
+ contour.style.left = 1;
+ contour.style.top = 1;
+ var stroke = document.createElement('v:stroke');
+ stroke.opacity = '20%';
+ contour.appendChild(stroke);
+
+ // Create a group and add the button and contour shapes to it
+ var g = document.createElement('v:group');
+ g.style.width = 5000;
+ g.style.height = 5000;
+ g.coordsize = '5000,5000';
+ g.style.left = pos.xpos();
+ g.style.top = pos.ypos();
+ g.appendChild(shape);
+ shape.appendChild(title);
+ g.appendChild(contour)
+ return g;
+ };
+
+ /**
+ * Return the relative position of a node.
+ */
+ graph.relpos = function(e) {
+ var curX = ui.csspos(e.style.left);
+ var curY = ui.csspos(e.style.top);
+ return graph.mkpath().move(curX, curY);
+ };
+
+ /**
+ * Move a node.
+ */
+ graph.move = function(e, pos) {
+ e.style.left = pos.xpos();
+ e.style.top = pos.ypos();
+ };
+
+} else {
+
+ /**
+ * SVG rendering.
+ */
+ graph.svgns='http://www.w3.org/2000/svg';
+
+ /**
+ * Make an SVG graph.
+ */
+ graph.mkgraph = function(pos, cname, pvalue) {
+
+ // Create a div element to host the graph
+ var div = document.createElement('div');
+ div.id = 'svgdiv';
+ div.style.position = 'absolute';
+ div.style.left = pos.xpos();
+ div.style.top = pos.ypos();
+ // -webkit-user-select: none;
+ document.body.appendChild(div);
+
+ // Create SVG element
+ var svg = document.createElementNS(graph.svgns, 'svg');
+ svg.style.height = 5000;
+ svg.style.width = 5000;
+ div.appendChild(svg);
+
+ // Track element dragging and selection
+ graph.dragging = null;
+ graph.selected = null;
+
+ /**
+ * Find the first draggable element in a hierarchy of elements.
+ */
+ function draggable(n) {
+ if (n == svg)
+ return null;
+ if (n.nodeName == 'g' && n.id != '')
+ return n;
+ return draggable(n.parentNode);
+ }
+
+ /**
+ * Handle a mouse down event.
+ */
+ svg.onmousedown = function(e) {
+ if (e.preventDefault)
+ e.preventDefault();
+ else
+ e.returnValue = false;
+
+ // Find draggable component
+ graph.dragging = draggable(e.target);
+ graph.selected = graph.dragging;
+ if (graph.dragging == null) {
+
+ // Reset current selection
+ cname.value = '';
+ pvalue.value = '';
+
+ // Trigger component select event
+ svg.oncompselect('');
+ return false;
+ }
+
+ // Clone component from the palette
+ var compos = scdl.composite(svg.compos);
+ if (graph.dragging.id.substring(0, 8) == 'palette:') {
+ graph.dragging = graph.clonepalette(graph.dragging, compos);
+ graph.selected = graph.dragging;
+ }
+
+ // Cut wire to component
+ if (graph.dragging.parentNode != svg)
+ setElement(compos, graph.cutwire(graph.dragging, compos, svg));
+
+ // Bring component to the top
+ graph.bringtotop(graph.dragging, svg);
+
+ // Remember current mouse position
+ var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
+ graph.dragX = pos.screenX;
+ graph.dragY = pos.screenY;
+
+ // Update the component name and property value fields
+ cname.value = graph.selected.id;
+ pvalue.value = graph.property(graph.selected.comp);
+
+ // Trigger component select event
+ svg.oncompselect(svg.appname, graph.selected.id);
+ return false;
+ };
+
+ // Support touch devices
+ svg.ontouchstart = svg.onmousedown;
+
+ /**
+ * Handle a mouse up event.
+ */
+ window.onmouseup = function(e) {
+ if (graph.dragging == null)
+ return false;
+
+ if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') {
+ var gpos = graph.relpos(graph.dragging);
+ if (gpos.xpos() >= trashcx) {
+
+ // If component close enough to editing area, move it there
+ if (gpos.xpos() < palcx)
+ graph.move(graph.dragging, graph.mkpath().move(palcx, gpos.ypos()));
+
+ // Add new dragged component to the composite
+ if (isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(svg.compos);
+ setElement(compos, graph.addcomp(graph.dragging.comp, compos));
+ graph.dragging.compos = svg.compos;
+ }
+
+ // Update component position
+ setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.dragging, svg)));
+
+ // Wire component to neighboring reference
+ if (!isNil(graph.dragging.svcpos)) {
+ var compos = scdl.composite(svg.compos);
+ setElement(compos, graph.clonerefs(graph.wire(graph.dragging, compos, svg)));
+ }
+
+ } else {
+
+ // Discard component dragged out of composite
+ svg.removeChild(graph.dragging);
+ if (!isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(svg.compos);
+ setElement(compos, graph.clonerefs(graph.gcollect(graph.removecomp(graph.dragging.comp, compos))));
+ }
+
+ // Reset current selection
+ graph.selected = null;
+ cname.value = '';
+ pvalue.value = '';
+
+ // Trigger component select event
+ svg.oncompselect('');
+ }
+ }
+
+ // Forget current dragged component
+ graph.dragging = null;
+
+ // Refresh the composite
+ graph.refresh(svg);
+
+ // Trigger composite change event
+ svg.oncomposchange();
+ return false;
+ };
+
+ // Support touch devices
+ window.top.onmouseup = window.onmouseup;
+ window.ontouchend = window.onmouseup;
+ window.gestureend = window.onmouseup;
+ window.top.gestureend = window.onmouseup;
+ window.top.ontouchend = window.onmouseup;
+ window.ontouchcancel = window.onmouseup;
+ window.top.ontouchcancel = window.onmouseup;
+
+ /**
+ * Handle a mouse move event.
+ */
+ window.onmousemove = function(e) {
+ if (graph.dragging == null)
+ return false;
+ if (e.preventDefault)
+ e.preventDefault();
+ else
+ e.returnValue = false;
+
+ // Calculate new position of dragged element
+ var gpos = graph.relpos(graph.dragging);
+ var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
+ var newX = gpos.xpos() + (pos.screenX - graph.dragX);
+ var newY = gpos.ypos() + (pos.screenY - graph.dragY);
+ if (newX >= 0)
+ graph.dragX = pos.screenX;
+ else
+ newX = 0;
+ if (newY >= 0)
+ graph.dragY = pos.screenY;
+ else
+ newY = 0;
+
+ // Move the dragged element
+ graph.move(graph.dragging, graph.mkpath().move(newX, newY));
+
+ return false;
+ };
+
+ // Support touch devices
+ window.top.onmousemove = window.onmousemove;
+ window.ontouchmove = window.onmousemove;
+ window.top.ontouchmove = window.onmousemove;
+
+ /**
+ * Handle field on change events.
+ */
+ cname.onchange = function() {
+ if (graph.selected == null)
+ return false;
+
+ // Change component name and refactor references to it
+ var compos = scdl.composite(svg.compos);
+ cname.value = graph.ucid(cname.value, compos);
+ graph.selected.id = cname.value;
+ setElement(compos, graph.renamecomp(graph.selected.comp, compos, cname.value));
+
+ // Trigger component select event
+ svg.oncompselect(svg.appname, graph.selected.id);
+
+ // Refresh the composite
+ graph.refresh(svg);
+
+ // Trigger composite change event
+ svg.oncomposchange();
+ return false;
+ };
+
+ pvalue.onchange = function() {
+ if (graph.selected == null)
+ return false;
+
+ // Change the component property value
+ graph.setproperty(graph.selected.comp, pvalue.value);
+ pvalue.value = graph.property(graph.selected.comp);
+
+ // Refresh the composite
+ graph.refresh(svg);
+
+ // Trigger composite change event
+ svg.oncomposchange();
+ return false;
+ };
+
+ // Create a hidden SVG element to help compute the width
+ // of component and reference titles
+ graph.titlewidthsvg = document.createElementNS(graph.svgns, 'svg');
+ graph.titlewidthsvg.style.visibility = 'hidden';
+ graph.titlewidthsvg.style.height = 0;
+ graph.titlewidthsvg.style.width = 0;
+ div.appendChild(graph.titlewidthsvg);
+
+ return svg;
+ };
+
+ /**
+ * Make a path.
+ */
+ graph.mkpath = function() {
+ function Path() {
+ this.BasePath = graph.BasePath;
+ this.BasePath();
+
+ this.clone = function() {
+ return graph.mkpath().pos(this.xpos(), this.ypos());
+ };
+
+ this.move = function(x, y) {
+ this.path += 'M' + x + ',' + y + ' ';
+ return this.pos(x, y);
+ };
+
+ this.line = function(x, y) {
+ this.path += 'L' + x + ',' + y + ' ';
+ return this.pos(x, y);
+ };
+
+ this.curve = function(x1, y1, x, y) {
+ this.path += 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y + ' ';
+ return this.pos(x, y);
+ };
+
+ this.end = function() {
+ this.path += 'Z';
+ return this;
+ };
+ }
+
+ return new Path();
+ };
+
+ /**
+ * Return an element representing a title.
+ */
+ graph.mktitle = function(t, bold) {
+ var title = document.createElementNS(graph.svgns, 'text');
+ title.setAttribute('text-anchor', 'start');
+ title.setAttribute('x', 5);
+ title.setAttribute('y', 15);
+ if (bold)
+ title.style.fontWeight = 'bold';
+ title.appendChild(document.createTextNode(t));
+ return title;
+ };
+
+ /**
+ * Return an element representing the title of a component.
+ */
+ graph.comptitle = function(comp) {
+ return graph.mktitle(graph.title(comp), false);
+ };
+
+ /**
+ * Return the width of the title of a component.
+ */
+ graph.comptitlewidth = function(comp) {
+ var title = graph.comptitle(comp);
+ graph.titlewidthsvg.appendChild(title);
+ var width = title.getBBox().width + 4;
+ graph.titlewidthsvg.removeChild(title);
+ return width;
+ };
+
+ /**
+ * Return an element representing the title of a reference.
+ */
+ graph.reftitle = function(ref) {
+ return graph.mktitle(graph.title(ref), false);
+ };
+
+ /**
+ * Return the width of the title of a reference.
+ */
+ graph.reftitlewidth = function(ref) {
+ var title = graph.reftitle(ref);
+ graph.titlewidthsvg.appendChild(title);
+ var width = title.getBBox().width;
+ graph.titlewidthsvg.removeChild(title);
+ return width;
+ };
+
+ /**
+ * Return an element representing the value of a property.
+ */
+ graph.proptitle = function(comp) {
+ var title = graph.mktitle(graph.property(comp), true);
+ title.setAttribute('y', 30);
+ return title;
+ };
+
+ /**
+ * Return the width of the title of a property.
+ */
+ graph.proptitlewidth = function(comp) {
+ var title = graph.proptitle(comp);
+ graph.titlewidthsvg.appendChild(title);
+ var width = title.getBBox().width + 4;
+ graph.titlewidthsvg.removeChild(title);
+ return width;
+ };
+
+ /**
+ * Return a node representing a component.
+ */
+ graph.compnode = function(comp, cassoc, pos) {
+
+ // Make the component and property title elements
+ var title = graph.comptitle(comp);
+ var prop = graph.proptitle(comp);
+
+ // Compute the path of the component shape
+ var path = graph.comppath(comp, cassoc);
+ var d = path.str();
+
+ // Create the main component shape
+ var shape = document.createElementNS(graph.svgns, 'path');
+ shape.setAttribute('d', d);
+ shape.setAttribute('fill', graph.color(comp));
+ shape.setAttribute('fill-opacity', '0.60');
+
+ // Create an overlay contour shape
+ var contour = document.createElementNS(graph.svgns, 'path');
+ contour.setAttribute('d', d);
+ contour.setAttribute('fill', 'none');
+ contour.setAttribute('stroke', graph.colors.gray);
+ contour.setAttribute('stroke-width', '3');
+ contour.setAttribute('stroke-opacity', '0.20');
+ contour.setAttribute('transform', 'translate(1,1)');
+
+ // Create a group and add the component and contour shapes to it.
+ var g = document.createElementNS(graph.svgns, 'g');
+ g.id = scdl.name(comp);
+ g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ g.appendChild(shape);
+ g.appendChild(contour);
+ g.appendChild(title);
+ g.appendChild(prop);
+
+ // Store the component and the positions of its services
+ // and references in the component shape
+ g.comp = comp;
+ g.refpos = reverse(path.refpos);
+ g.svcpos = reverse(path.svcpos);
+
+ return g;
+ };
+
+ /**
+ * Return a graphical group.
+ */
+ graph.mkgroup = function(pos) {
+ var g = document.createElementNS(graph.svgns, 'g');
+ g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ return g;
+ };
+
+ /**
+ * Return a node representing a button.
+ */
+ graph.mkbutton = function(t, pos) {
+
+ // Make the button title
+ var title = graph.mktitle(t, true);
+
+ // Compute the path of the button shape
+ var path = graph.buttonpath().str();
+
+ // Create the main button shape
+ var shape = document.createElementNS(graph.svgns, 'path');
+ shape.setAttribute('d', path);
+ shape.setAttribute('fill', graph.colors.lightgray);
+ shape.setAttribute('fill-opacity', '0.60');
+
+ // Create an overlay contour shape
+ var contour = document.createElementNS(graph.svgns, 'path');
+ contour.setAttribute('d', path);
+ contour.setAttribute('fill', 'none');
+ contour.setAttribute('stroke', graph.colors.gray);
+ contour.setAttribute('stroke-width', '3');
+ contour.setAttribute('stroke-opacity', '0.20');
+ contour.setAttribute('transform', 'translate(1,1)');
+
+ // Create a group and add the button and contour shapes to it
+ var g = document.createElementNS(graph.svgns, 'g');
+ g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ g.appendChild(shape);
+ g.appendChild(contour);
+ g.appendChild(title);
+ return g;
+ };
+
+ /**
+ * Return the relative position of a node.
+ */
+ graph.relpos = function(e) {
+ var pmatrix = e.parentNode.getCTM();
+ var matrix = e.getCTM();
+ var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
+ var curY = pmatrix != null? (Number(matrix.f) - Number(pmatrix.f)): Number(matrix.f);
+ return graph.mkpath().move(curX, curY);
+ };
+
+ /**
+ * Move a node.
+ */
+ graph.move = function(e, pos) {
+ e.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ };
+};
+
+/**
+ * Return the absolute position of a component node.
+ */
+graph.abspos = function(e, g) {
+ if (e == g)
+ return graph.mkpath();
+ var gpos = graph.relpos(e);
+ var pgpos = graph.abspos(e.parentNode, g);
+ return graph.mkpath().move(gpos.xpos() + pgpos.xpos(), gpos.ypos() + pgpos.ypos());
+};
+
+/**
+ * Bring a component node to the top.
+ */
+graph.bringtotop = function(n, g) {
+ if (n == g)
+ return null;
+ graph.move(n, graph.abspos(n, g));
+ g.appendChild(n);
+}
+
+/**
+ * Return the title of a SCDL element.
+ */
+graph.title = function(e) {
+ var t = scdl.title(e);
+ if (t != null) {
+ if (t == 'gt')
+ return '>'
+ if (t == 'lt')
+ return '<';
+ return t;
+ }
+ return scdl.name(e);
+};
+
+/**
+ * Return the property value of a SCDL component.
+ */
+graph.property = function(e) {
+ var p = scdl.properties(e);
+ if (isNil(p))
+ return '';
+ if (scdl.visible(car(p)) == 'false')
+ return '';
+ return scdl.propertyValue(car(p));
+};
+
+/**
+ * Change the property value of a SCDL component.
+ */
+graph.setproperty = function(e, value) {
+ var p = scdl.properties(e);
+ if (isNil(p))
+ return '';
+ if (scdl.visible(car(p)) == 'false')
+ return '';
+ var name = scdl.name(car(p));
+ setElement(car(p), mklist(element, "'property", mklist(attribute, "'name", name != null? name : "property"), value));
+ return value;
+};
+
+/**
+ * Return the color of a SCDL component.
+ */
+graph.color = function(comp) {
+ return memo(comp, 'color', function() {
+ var c = scdl.color(comp);
+ return c == null? graph.colors.blue1 : graph.colors[c];
+ });
+};
+
+/**
+ * Return the services on the top side of a component.
+ */
+graph.tsvcs = function(comp) {
+ return memo(comp, 'tsvcs', function() {
+ var svcs = scdl.services(comp);
+ var l = filter(function(s) { return scdl.align(s) == 'top' && scdl.visible(s) != 'false'; }, svcs);
+ if (isNil(l))
+ return mklist();
+ return mklist(car(l));
+ });
+};
+
+/**
+ * Return the services on the left side of a component.
+ */
+graph.lsvcs = function(comp) {
+ return memo(comp, 'lsvcs', function() {
+ var svcs = scdl.services(comp);
+ if (isNil(svcs))
+ return mklist(mklist("'element","'service","'attribute","'name",scdl.name(comp)));
+ var l = filter(function(s) {
+ var a = scdl.align(s);
+ var v = scdl.visible(s);
+ return (a == null || a == 'left') && v != 'false';
+ }, svcs);
+ if (isNil(l))
+ return mklist();
+ if (!isNil(graph.tsvcs(comp)))
+ return mklist();
+ return mklist(car(l));
+ });
+};
+
+/**
+ * Return the references on the bottom side of a component.
+ */
+graph.brefs = function(comp) {
+ return memo(comp, 'brefs', function() {
+ return filter(function(r) { return scdl.align(r) == 'bottom' && scdl.visible(r) != 'false'; }, scdl.references(comp));
+ });
+};
+
+/**
+ * Return the references on the right side of a component.
+ */
+graph.rrefs = function(comp) {
+ return memo(comp, 'rrefs', function() {
+ return filter(function(r) {
+ var a = scdl.align(r);
+ var v = scdl.visible(r);
+ return (a == null || a == 'right') && v != 'false';
+ }, scdl.references(comp));
+ });
+};
+
+/**
+ * Return the height of a reference on the right side of a component.
+ */
+graph.rrefheight = function(ref, cassoc) {
+ return memo(ref, 'rheight', function() {
+ var target = assoc(scdl.target(ref), cassoc);
+ if (isNil(target))
+ return tabsz * 10;
+ return graph.compclosureheight(cadr(target), cassoc);
+ });
+};
+
+/**
+ * Return the height of a reference on the bottom side of a component.
+ */
+graph.brefheight = function(ref, cassoc) {
+ return memo(ref, 'bheight', function() {
+ var target = assoc(scdl.target(ref), cassoc);
+ if (isNil(target))
+ return 0;
+ return graph.compclosureheight(cadr(target), cassoc);
+ });
+};
+
+/**
+ * Return the total height of the references on the right side of a component.
+ */
+graph.rrefsheight = function(refs, cassoc) {
+ if (isNil(refs))
+ return 0;
+ return graph.rrefheight(car(refs), cassoc) + graph.rrefsheight(cdr(refs), cassoc);
+};
+
+/**
+ * Return the max height of the references on the bottom side of a component.
+ */
+graph.brefsheight = function(refs, cassoc) {
+ if (isNil(refs))
+ return 0;
+ return Math.max(graph.brefheight(car(refs), cassoc), graph.brefsheight(cdr(refs), cassoc));
+};
+
+/**
+ * Return the height of a component node.
+ */
+graph.compheight = function(comp, cassoc) {
+ return memo(comp, 'height', function() {
+ var lsvcs = graph.lsvcs(comp);
+ var lsvcsh = Math.max(1, length(lsvcs)) * (tabsz * 10) + (tabsz * 4);
+ var rrefs = graph.rrefs(comp);
+ var rrefsh = graph.rrefsheight(rrefs, cassoc) + (tabsz * 4);
+ var height = Math.max(lsvcsh, rrefsh);
+ if (!isNil(graph.brefs(comp)))
+ height = Math.max(height, (tabsz * 10) + (tabsz * 4) + (tabsz * 2));
+ if (graph.property(comp) != '')
+ height = Math.max(40, height);
+ return height;
+ });
+};
+
+/**
+ * Return the height of a component and the components wired to its bottom side.
+ */
+graph.compclosureheight = function(comp, cassoc) {
+ return memo(comp, 'closureheight', function() {
+ var brefs = graph.brefs(comp);
+ var height = graph.compheight(comp, cassoc) + graph.brefsheight(brefs, cassoc);
+ return height;
+ });
+};
+
+/**
+ * Return the width of a reference on the bottom side of a component.
+ */
+graph.brefwidth = function(ref, cassoc) {
+ return memo(ref, 'width', function() {
+ var target = assoc(scdl.target(ref), cassoc);
+ if (isNil(target))
+ return tabsz * 10;
+ return graph.compclosurewidth(cadr(target), cassoc);
+ });
+};
+
+/**
+ * Return the total width of the references on the bottom side of a component.
+ */
+graph.brefswidth = function(refs, cassoc) {
+ if (isNil(refs))
+ return 0;
+ return graph.brefwidth(car(refs), cassoc) + graph.brefswidth(cdr(refs), cassoc);
+};
+
+/**
+ * Return the max width of the references on the right side of a component.
+ */
+graph.rrefswidth = function(refs, cassoc) {
+ if (isNil(refs))
+ return 0;
+ return Math.max(graph.brefwidth(car(refs), cassoc), graph.rrefswidth(cdr(refs), cassoc));
+};
+
+/**
+ * Return the width of a component.
+ */
+graph.compwidth = function(comp, cassoc) {
+ return memo(comp, 'width', function() {
+ var twidth = Math.max(graph.comptitlewidth(comp), graph.proptitlewidth(comp)) + (tabsz * 8);
+ var tsvcs = graph.tsvcs(comp);
+ var tsvcsw = Math.max(1, length(tsvcs)) * (tabsz * 10) + (tabsz * 4);
+ var brefs = graph.brefs(comp);
+ var brefsw = graph.brefswidth(brefs, cassoc) + (tabsz * 4);
+ var width = Math.max(twidth, Math.max(tsvcsw, brefsw));
+ return width;
+ });
+};
+
+/**
+ * Return the width of a component and all the components wired to its right side.
+ */
+graph.compclosurewidth = function(comp, cassoc) {
+ return memo(comp, 'closurewidth', function() {
+ var rrefs = graph.rrefs(comp);
+ var width = graph.compwidth(comp, cassoc) + graph.rrefswidth(rrefs, cassoc);
+ return width;
+ });
+};
+
+/**
+ * Return a path representing a reference positioned to the right of a component.
+ */
+graph.rrefpath = function(ref, cassoc, path) {
+ var height = graph.rrefheight(ref, cassoc);
+
+ // Record reference position in the path
+ var xpos = path.xpos();
+ var ypos = path.ypos();
+ //path.refpos = cons(mklist(ref, graph.mkpath().move(xpos, ypos + (tabsz * 6))), path.refpos);
+ path.refpos = cons(mklist(ref, graph.mkpath().move(xpos, ypos + (tabsz * 5))), path.refpos);
+
+ // Compute the reference path
+ return path.rline(0,tabsz).rline(0,tabsz * 2).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz).rline(0,tabsz * 4).rcurve(0,tabsz,tabsz,0).rcurve(tabsz,0,0,-tabsz).rcurve(0,-tabsz,tabsz,0).rcurve(tabsz,0,0,tabsz).line(path.xpos(),ypos + height);
+};
+
+/**
+ * Return a path representing a reference positioned at the bottom of a component.
+ */
+graph.brefpath = function(ref, cassoc, path) {
+ var width = graph.brefwidth(ref, cassoc);
+
+ // Record reference position in the path
+ var xpos = path.xpos();
+ var ypos = path.ypos();
+ path.refpos = cons(mklist(ref, graph.mkpath().move(xpos - width + tabsz * 5, ypos)), path.refpos);
+
+ // Compute the reference path
+ return path.line(xpos - width + (tabsz * 10),path.ypos()).rline(-tabsz,0).rline(-(tabsz *2),0).rcurve(-tabsz,0,0,-tabsz).rcurve(0,-tabsz,tabsz,0).rcurve(tabsz,0,0,-tabsz).rcurve(0,-tabsz,-tabsz,0).rline(-(tabsz * 4),0).rcurve(-tabsz,0,0,tabsz).rcurve(0,tabsz,tabsz,0).rcurve(tabsz,0,0,tabsz).rcurve(0,tabsz,-tabsz,0).line(xpos - width,path.ypos());
+};
+
+/**
+ * Return a path representing a service positioned to the left of a component.
+ */
+graph.lsvcpath = function(svc, cassoc, path) {
+ var height = tabsz * 10;
+
+ // Record service position in the path
+ var xpos = path.xpos();
+ var ypos = path.ypos();
+ path.svcpos = cons(mklist(svc, graph.mkpath().move(xpos, ypos - (tabsz * 6))), path.svcpos);
+
+ // Compute the service path
+ return path.rline(0,-tabsz).rline(0, -(tabsz * 2)).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz).rline(0,-(tabsz * 4)).rcurve(0,-tabsz,tabsz,0).rcurve(tabsz,0,0,tabsz).rcurve(0,tabsz,tabsz,0).rcurve(tabsz,0,0,-tabsz).line(path.xpos(), ypos - height);
+};
+
+/**
+ * Return a path representing a service positioned at the top of a component.
+ */
+graph.tsvcpath = function(svc, cassoc, path) {
+ var width = tabsz * 10;
+
+ // Record service position in the path
+ var xpos = path.xpos();
+ var ypos = path.ypos();
+ path.svcpos = cons(mklist(svc, graph.mkpath().move(xpos + (tabsz * 5), ypos)), path.svcpos);
+
+ // Compute the service path
+ return path.rline(tabsz,0).rline(tabsz * 2,0).rcurve(tabsz,0,0,-tabsz).rcurve(0,-tabsz,-tabsz,0).rcurve(-tabsz,0,0,-tabsz).rcurve(0,-tabsz,tabsz,0).rline(tabsz * 4,0).rcurve(tabsz,0,0,tabsz).rcurve(0,tabsz,-tabsz,0).rcurve(-tabsz,0,0,tabsz).rcurve(0,tabsz,tabsz,0).line(xpos + width,path.ypos());
+};
+
+/**
+ * Return a path representing a component node.
+ */
+graph.comppath = function(comp, cassoc) {
+
+ // Calculate the width and height of the component node
+ var width = graph.compwidth(comp, cassoc);
+ var height = graph.compheight(comp, cassoc);
+
+ /**
+ * Apply a path rendering function to a list of services or references.
+ */
+ function renderpath(x, f, cassoc, path) {
+ if (isNil(x))
+ return path;
+ return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path));
+ }
+
+ var path = graph.mkpath().move(curvsz,0);
+
+ // Store the positions of services and references in the path
+ path.refpos = mklist();
+ path.svcpos = mklist();
+
+ // Render the services on the top side of the component
+ var tsvcs = graph.tsvcs(comp);
+ path = renderpath(tsvcs, graph.tsvcpath, cassoc, path);
+
+ // Render the references on the right side of the component
+ var rrefs = graph.rrefs(comp);
+ path = path.line(width - curvsz,path.ypos()).rcurve(curvsz,0,0,curvsz);
+ path = renderpath(rrefs, graph.rrefpath, cassoc, path);
+
+ // Render the references on the bottom side of the component
+ var brefs = reverse(graph.brefs(comp));
+ var boffset = curvsz + graph.brefswidth(brefs, cassoc);
+ path = path.line(path.xpos(),height - curvsz).rcurve(0,curvsz,curvsz * -1,0).line(boffset, path.ypos());
+ path = renderpath(brefs, graph.brefpath, cassoc, path);
+
+ // Render the services on the left side of the component
+ var lsvcs = graph.lsvcs(comp);
+ var loffset = curvsz + (length(lsvcs) * (tabsz * 10));
+ path = path.line(curvsz,path.ypos()).rcurve(curvsz * -1,0,0,curvsz * -1).line(path.xpos(), loffset);
+ path = renderpath(lsvcs, graph.lsvcpath, cassoc, path);
+
+ // Close the component node path
+ path = path.line(0,curvsz).rcurve(0,curvsz * -1,curvsz,0);
+
+ return path.end();
+};
+
+/**
+ * Return a path representing a button node.
+ */
+graph.buttonpath = function(t) {
+ var path = graph.mkpath().move(curvsz,0);
+ path = path.line(buttoncx - curvsz,path.ypos()).rcurve(curvsz,0,0,curvsz);
+ path = path.line(path.xpos(),buttoncy - curvsz).rcurve(0,curvsz,-curvsz,0).line(curvsz, path.ypos());
+ path = path.line(curvsz,path.ypos()).rcurve(-curvsz,0,0,-curvsz).line(path.xpos(), curvsz);
+ path = path.line(0,curvsz).rcurve(0,-curvsz,curvsz,0);
+ return path.end();
+};
+
+/**
+ * Render a SCDL composite into a list of component nodes.
+ */
+graph.composite = function(compos, pos) {
+ var name = scdl.name(scdl.composite(compos));
+ var comps = scdl.components(compos);
+ var cassoc = scdl.nameToElementAssoc(comps);
+ var proms = scdl.promotions(compos);
+
+ /**
+ * Render a component.
+ */
+ function rendercomp(comp, cassoc, pos) {
+
+ /**
+ * Render the references on the right side of a component.
+ */
+ function renderrrefs(refs, cassoc, pos, gcomp) {
+
+ /**
+ * Render a reference on the right side of a component.
+ */
+ function renderrref(ref, cassoc, pos, gcomp) {
+ var target = assoc(scdl.target(ref), cassoc);
+ if (isNil(target))
+ return null;
+
+ // Render the component target of the reference
+ return rendercomp(cadr(target), cassoc, pos);
+ }
+
+ /**
+ * Move the rendering cursor down below a reference.
+ */
+ function rendermove(ref, cassoc, pos) {
+ return pos.clone().rmove(0, graph.rrefheight(ref, cassoc));
+ }
+
+ if (isNil(refs))
+ return mklist();
+
+ // Return list of (ref, comp rendering) pairs
+ var grefcomp = renderrref(car(refs), cassoc, pos, gcomp);
+ return cons(mklist(car(refs), grefcomp), renderrrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp));
+ }
+
+ /**
+ * Render the references on the bottom side of a component.
+ */
+ function renderbrefs(refs, cassoc, pos, gcomp) {
+
+ /**
+ * Render a reference on the bottom side of a component.
+ */
+ function renderbref(ref, cassoc, pos, gcomp) {
+ var target = assoc(scdl.target(ref), cassoc);
+ if (isNil(target))
+ return null;
+
+ // Render the component target of the reference
+ return rendercomp(cadr(target), cassoc, pos);
+ }
+
+ /**
+ * Move the rendering cursor to the right of a reference.
+ */
+ function rendermove(ref, cassoc, pos) {
+ return pos.clone().rmove(graph.brefwidth(ref, cassoc), 0);
+ }
+
+ if (isNil(refs))
+ return mklist();
+
+ // Return list of (ref, comp rendering) pairs
+ var grefcomp = renderbref(car(refs), cassoc, pos, gcomp);
+ return cons(mklist(car(refs), grefcomp), renderbrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos), gcomp));
+ }
+
+ // Compute the component shape
+ var gcomp = graph.compnode(comp, cassoc, pos);
+
+ // Render the components wired to the component references
+ var rrefs = graph.rrefs(comp);
+ var rpos = graph.mkpath().rmove(graph.compwidth(comp, cassoc), 0);
+ var grrefs = renderrrefs(rrefs, cassoc, rpos, gcomp);
+
+ var brefs = graph.brefs(comp);
+ var bpos = graph.mkpath().rmove(0 , graph.compheight(comp, cassoc));
+ var gbrefs = renderbrefs(brefs, cassoc, bpos, gcomp);
+
+ // Store list of (ref, pos, component rendering) triplets in the component
+ function refposgcomp(refpos, grefs) {
+ if (isNil(refpos))
+ return mklist();
+
+ // Append component rendering to component
+ var gref = cadr(car(grefs));
+ if (gref != null)
+ appendNodes(mklist(gref), gcomp);
+ return cons(mklist(car(car(refpos)), cadr(car(refpos)), gref), refposgcomp(cdr(refpos), cdr(grefs)));
+ }
+
+ gcomp.refpos = refposgcomp(gcomp.refpos, append(grrefs, gbrefs));
+
+ return gcomp;
+ }
+
+ /**
+ * Render a list of promoted service components.
+ */
+ function renderproms(svcs, cassoc, pos) {
+
+ /**
+ * Return the component promoted by a service.
+ */
+ function promcomp(svc, cassoc) {
+ var c = assoc(scdl.promote(svc), cassoc);
+ if (isNil(c))
+ return mklist();
+ return cadr(c);
+ }
+
+ /**
+ * Return the position of a component.
+ */
+ function comppos(comp, pos) {
+ var x = scdl.x(comp);
+ var y = scdl.y(comp);
+ return graph.mkpath().move(
+ x != null? Number(x) + palcx : pos.xpos(),
+ y != null? Number(y) : (isNil(graph.tsvcs(comp))? pos.ypos() : pos.ypos() + (tabsz * 4)));
+ }
+
+ /**
+ * Move the rendering cursor down below a component.
+ */
+ function rendermove(comp, cassoc, pos) {
+ return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + Math.max((tabsz * 2), 8));
+ }
+
+ if (isNil(svcs))
+ return mklist();
+
+ // Render the first promoted component in the list
+ // then recurse to render the rest of the list
+ var comp = promcomp(car(svcs), cassoc);
+ if (isNil(comp))
+ return renderproms(cdr(svcs), cassoc, rendermove(car(svcs), cassoc, pos));
+
+ var cpos = comppos(comp, pos);
+ return cons(rendercomp(comp, cassoc, cpos), renderproms(cdr(svcs), cassoc, rendermove(comp, cassoc, cpos)));
+ }
+
+ // Render the promoted service components
+ var rproms = renderproms(proms, cassoc, pos.clone().rmove(tabsz * 4, tabsz * 4));
+
+ if (name == 'palette') {
+
+ // Prefix ids of palette component elements with 'palette:'
+ return map(function(r) { r.id = 'palette:' + r.id; return r; }, rproms);
+ } else {
+
+ // Link app component elements to the containing composite
+ return map(function(r) { r.compos = compos; return r; }, rproms);
+ }
+};
+
+/**
+ * Return a component unique id.
+ */
+graph.ucid = function(prefix, compos) {
+
+ // Build an assoc list keyed by component name
+ var comps = map(function(c) { return mklist(scdl.name(c), c); }, namedElementChildren("'component", compos));
+
+ /**
+ * Find a free component id.
+ */
+ function ucid(p, id) {
+ if (isNil(assoc(p + id, comps)))
+ return p + id;
+ return ucid(p, id + 1);
+ }
+
+ if (isNil(assoc(prefix, comps)))
+ return prefix;
+
+ return ucid(prefix == ''? 'comp' : prefix, 2);
+};
+
+/**
+ * Clone a palette component node.
+ */
+graph.clonepalette = function(e, compos) {
+
+ // Clone the SCDL component and give it a unique name
+ var comp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos))),
+ filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp)));
+ var x = '<composite xmlns:t="http://tuscany.apache.org/xmlns/sca/1.1">' + writeXML(mklist(comp), false) + '</composite>';
+ var compos = readXML(mklist(x));
+ comp = car(scdl.components(compos));
+
+ // Make a component node
+ var gcomp = graph.compnode(comp, mklist(), graph.mkpath());
+ graph.move(gcomp, graph.relpos(e));
+ e.parentNode.appendChild(gcomp);
+
+ return gcomp;
+};
+
+/**
+ * Move a SCDL component to the given position.
+ */
+graph.movecomp = function(comp, pos) {
+ return append(mklist(element, "'component", mklist(attribute, "'t:x", '' + (pos.xpos() - palcx)), mklist(attribute, "'t:y", '' + pos.ypos())),
+ filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'t:x" || attributeName(e) == "'t:y")); }, elementChildren(comp)));
+};
+
+/**
+ * Add a component to a SCDL composite.
+ */
+graph.addcomp = function(comp, compos) {
+ var name = scdl.name(comp);
+ var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
+ return append(mklist(element, "'composite"), append(elementChildren(compos), mklist(prom, comp)));
+};
+
+/**
+ * Remove a component from a SCDL composite.
+ */
+graph.removecomp = function(comp, compos) {
+ var name = scdl.name(comp);
+ return append(mklist(element, "'composite"),
+ filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos)));
+};
+
+/**
+ * Garbage collect components not referenced or promoted.
+ */
+graph.gcollect = function(compos) {
+
+ // List the promoted components
+ var proms = map(function(s) { return mklist(scdl.promote(s), true); }, scdl.promotions(mklist(compos)));
+
+ // List the referenced components
+ var refs = reduce(function(a, comp) {
+ return append(a,
+ map(function(ref) { return mklist(scdl.target(ref), true); }, filter(function(ref) { return scdl.target(ref) != null; }, scdl.references(comp))));
+ }, mklist(), scdl.components(mklist(compos)));
+
+ // Filter out the unused components
+ var used = append(proms, refs);
+ return append(mklist(element, "'composite"),
+ filter(function(c) { return !(isElement(c) && elementName(c) == "'component" && isNil(assoc(scdl.name(c), used))); }, elementChildren(compos)));
+}
+
+/**
+ * Clone and cleanup clonable references.
+ */
+graph.clonerefs = function(compos) {
+ return append(mklist(element, "'composite"),
+ map(function(c) {
+ if (elementName(c) != "'component")
+ return c;
+
+ // If the references are clonable
+ var refs = scdl.references(c);
+ if (isNil(refs))
+ return c;
+ if (scdl.clonable(car(refs)) != 'true')
+ return c;
+
+ // Filter out the unwired references and add a fresh unwired
+ // reference at the end of the list
+ var cc = append(
+ filter(function(e) { return !(elementName(e) == "'reference" && scdl.target(e) == null); }, elementChildren(c)),
+ mklist(mklist(element, "'reference", mklist(attribute, "'name", scdl.name(car(refs))), mklist(attribute, "'t:clonable", "true"))));
+ return append(mklist(element, "'component"), cc);
+
+ }, elementChildren(compos)));
+}
+
+/**
+ * Rename a component.
+ */
+graph.renamecomp = function(comp, compos, name) {
+
+ /**
+ * Refactor references to a component.
+ */
+ function refactorrefs(refs, oname, nname) {
+ if (isNil(refs))
+ return true;
+ var ref = car(refs);
+ if (scdl.target(ref) != oname)
+ return refactorrefs(cdr(refs), oname, nname);
+
+ // Change the reference's target attribute
+ setElement(ref, append(mklist(element, "'reference"),
+ append(filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(ref)),
+ mklist(mklist(attribute, "'target", nname)))));
+
+ return refactorrefs(cdr(refs), oname, nname);
+ }
+
+ // Refactor all the references to the renamed component
+ var oname = scdl.name(comp);
+ map(function(c) { return refactorrefs(scdl.references(c), oname, name); }, namedElementChildren("'component", compos));
+
+ // Rename the SCDL promoted service and component
+ var proms = filter(function(s) { return scdl.name(s) == oname }, scdl.services(compos));
+ if (!isNil(proms))
+ setElement(car(proms), mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name)));
+ setElement(comp, append(mklist(element, "'component"),
+ cons(mklist(attribute, "'name", name),
+ filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'name"); }, elementChildren(comp)))));
+
+ return append(mklist(element, "'composite"), elementChildren(compos));
+};
+
+/**
+ * Cut the wire to a component node and make that node a
+ * top level component node.
+ */
+graph.cutwire = function(node, compos, g) {
+
+ /**
+ * Find the reference wired to a node and cut its wire.
+ */
+ function cutref(refs, node) {
+ if (isNil(refs))
+ return true;
+ var ref = car(refs);
+ if (caddr(ref) == node) {
+ setlist(ref, mklist(car(ref), cadr(ref), null));
+ setElement(car(ref),
+ append(mklist(element, "'reference"),
+ filter(function(e) { return !(isAttribute(e) && attributeName(e) == "'target"); }, elementChildren(car(ref)))));
+ }
+ return cutref(cdr(refs), node);
+ }
+
+ // Cut any reference wire, if found
+ cutref(node.parentNode.refpos, node);
+
+ // Make the component node a top level node.
+ node.compos = g.compos;
+
+ // Update the SCDL composite, add a promote element for
+ // that component
+ var comp = node.comp;
+ var name = scdl.name(comp);
+ var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
+ return append(mklist(element, "'composite"),
+ append(filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos)), mklist(prom, comp)));
+}
+
+/**
+ * Wire a component to the closest neighbor reference.
+ */
+graph.wire = function(n, compos, g) {
+
+ // Compute position of the component's service node
+ var spos = cadr(car(n.svcpos));
+ var aspos = graph.abspos(n, g).rmove(spos.xpos(), spos.ypos());
+
+ /**
+ * Find closest unwired reference node among all the references
+ * of all the components.
+ */
+ function closecomprefs(nodes, spos, cref) {
+
+ /**
+ * Find the closest unwired reference node among all the
+ * references of a node.
+ */
+ function closerefs(npos, refs, spos, cref) {
+ if (isNil(refs))
+ return cref;
+ var fdist = cadddr(cref);
+ var ref = car(refs);
+
+ // Skip wired reference
+ if (!isNil(filter(function(n) { return isAttribute(n) && attributeName(n) == "'target"; }, car(ref))))
+ return closerefs(npos, cdr(refs), spos, cref);
+
+ // Compute distance between service node and reference node
+ var rpos = cadr(ref).clone().rmove(npos.xpos(), npos.ypos());
+ var dx = Math.pow(rpos.xpos() - spos.xpos(), 2);
+ var dy = Math.pow(rpos.ypos() - spos.ypos(), 2);
+
+ // Check for proximity threshold
+ var rdist = (dx < (proxcx * proxcx) && dy < (proxcy * proxcy))? Math.sqrt(dx + dy) : 25000000;
+
+ // Go through all the references in the component
+ return closerefs(npos, cdr(refs), spos, fdist < rdist? cref : mklist(car(ref), cadr(ref), caddr(ref), rdist));
+ }
+
+ if (isNil(nodes))
+ return cref;
+
+ // Skip non-component nodes
+ var node = car(nodes);
+ if (isNil(node.comp))
+ return closecomprefs(cdr(nodes), spos, cref);
+
+ // Compute the component absolute position
+ var npos = graph.abspos(node, g);
+
+ // Go through all the components and their references
+ return closecomprefs(append(nodeList(node.childNodes), cdr(nodes)), spos, closerefs(npos, node.refpos, spos, cref));
+ }
+
+ // Find closest reference node
+ var cref = closecomprefs(nodeList(g.childNodes), aspos, mklist(null, graph.mkpath(), null, 25000000));
+ if (car(cref) == null)
+ return compos;
+ if (cadddr(cref) == 25000000)
+ return compos;
+
+ // Wire component to that reference, un-promote it, and
+ // update the SCDL reference and composite
+ n.compos = null;
+ setElement(car(cref), append(mklist(element, "'reference", mklist(attribute, "'target", scdl.name(n.comp))), elementChildren(car(cref))));
+ var name = scdl.name(n.comp);
+ return append(mklist(element, "'composite"),
+ filter(function(c) { return !(isElement(c) && elementName(c) == "'service" && scdl.name(c) == name); }, elementChildren(compos)));
+}
+
+/**
+ * Display a list of graphical nodes.
+ */
+graph.display = function(nodes, g) {
+
+ // Append the nodes to the graphical canvas
+ appendNodes(nodes, g);
+ return nodes;
+};
+
+/**
+ * Hide a graph.
+ */
+graph.hide = function(g) {
+
+ // Remove nodes from the graph
+ map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
+ return g;
+};
+
+/**
+ * Refresh a graph.
+ */
+graph.refresh = function(g) {
+
+ // Remove nodes and redisplay the composite associated with the graph
+ map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
+ graph.display(graph.composite(g.compos, graph.mkpath().move(palcx,0)), g);
+ return g;
+};
+
+/**
+ * Display and enable editing of a composite and the graphical
+ * nodes that represent it.
+ */
+graph.edit = function(appname, compos, nodes, onchange, onselect, g) {
+
+ // Store the appname and composite in the graphical canvas
+ g.appname = appname;
+ g.compos = compos;
+
+ // Store event listeners
+ g.oncomposchange = onchange;
+ g.oncompselect = onselect;
+
+ // Display the composite nodes
+ return graph.display(nodes, g);
+};
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/index.html b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/index.html
new file mode 100644
index 0000000000..7a06a63872
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/index.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+<title>Tuscany Extensions</title>
+
+<script type="text/javascript" src="/dojo/dojo.js"></script>
+<script type="text/javascript" src="util.js"></script>
+<script type="text/javascript" src="elemutil.js"></script>
+<script type="text/javascript" src="xmlutil.js"></script>
+<script type="text/javascript" src="atomutil.js"></script>
+<script type="text/javascript" src="scdl.js"></script>
+<script type="text/javascript" src="ui.js"></script>
+<script type="text/javascript" src="component.js"></script>
+<script type="text/javascript" src="graph.js"></script>
+
+<style type="text/css">
+body,html {
+ font-family: helvetica, arial, sans-serif;
+ font-size: 90%;
+}
+</style>
+
+
+</head>
+
+<body>
+
+<div id="bodydiv" style="position: absolute; top: 0px; left: 0px; right: 0px;">
+
+<div style="position:absolute; top: 40px; left: 240px; right: 0px; height: 5000px;">
+<iframe id="dataFrame" class="databg" style="position: relative; height: 5000px; width: 100%; border: 0px;" scrolling="no" frameborder="0"></iframe>
+</div>
+
+</div>
+
+
+
+
+<script type="text/javascript">
+ dojo.require("dojox.xml.parser");
+ dojo.require("tuscany.RestService");
+
+ var composite;
+
+ function composite_getResponse(xmlResponse, exception) {
+ if(exception){
+ alert(exception.message);
+ return;
+ }
+
+ /*
+ var itemTags = xmlResponse.getElementsByTagName ("component");
+ for (i = 0; i < itemTags.length; i++) {
+ var attributes = itemTags[i].attributes;
+ alert(attributes[0].name);
+ var recordNode = itemTags[i].getElementsByTagName ("name")[0];
+ if (recordNode.textContent != undefined) {
+ alert(recordNode.textContent);
+ }
+ else {
+ alert(recordNode.text);
+ }
+ }
+ */
+
+ composite = dojox.xml.parser.innerXML(xmlResponse);
+
+
+ alert(composite);
+
+ }
+
+
+ var restClient = new tuscany.RestService("http://localhost:8080/domain","text/plain");
+ restClient.get("default").addCallback(composite_getResponse);
+
+ // Create editor graph area
+ var g = graph.mkgraph(graph.mkpath().move(0,40), 'Domain', 'propValue');
+ var bg = graph.mkgroup(graph.mkpath());
+
+ graph.edit(name, composite, graph.composite('domain', graph.mkpath().move(palcx,0)), oncomposchange, oncompselect, g);
+ //graph.composite('domain', graph.mkpath().move(palcx,0));
+
+
+
+</script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/jsonutil.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/jsonutil.js
new file mode 100644
index 0000000000..47e3ec4130
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/jsonutil.js
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * JSON data conversion functions.
+ */
+var json = {};
+
+/**
+ * JSON exceptions.
+ */
+json.Exception = function(code, message) {
+ this.name = "JSONException";
+ this.code = code;
+ this.message = message;
+};
+
+json.Exception.prototype = new Error();
+
+json.Exception.prototype.toString = function() {
+ return this.name + ": " + this.message;
+};
+
+/**
+ * Return true if a list represents a JS array.
+ */
+json.isJSArray = function(l) {
+ if (isNil(l))
+ return true;
+ var v = car(l);
+ if (isSymbol(v))
+ return false;
+ if (isList(v))
+ if (!isNil(v) && isSymbol(car(v)))
+ return false;
+ return true;
+};
+
+/**
+ * Converts JSON properties to values.
+ */
+json.jsPropertiesToValues = function(propertiesSoFar, o, i) {
+ if (isNil(i))
+ return propertiesSoFar;
+ var p = car(i);
+ var jsv = o[p];
+ var v = json.jsValToValue(jsv);
+
+ if (typeof p == 'string') {
+ var n = '' + p;
+ if (n.slice(0, 1) == '@')
+ return json.jsPropertiesToValues(cons(mklist(attribute, "'" + n.slice(1), v), propertiesSoFar), o, cdr(i));
+ if (isList(v) && !json.isJSArray(v))
+ return json.jsPropertiesToValues(cons(cons(element, cons("'" + n, v)), propertiesSoFar), o, cdr(i));
+ return json.jsPropertiesToValues(cons(mklist(element, "'" + n, v), propertiesSoFar), o, cdr(i));
+ }
+ return json.jsPropertiesToValues(cons(v, propertiesSoFar), o, cdr(i));
+};
+
+/**
+ * Converts a JSON val to a value.
+ */
+json.jsValToValue = function(jsv) {
+ if (isList(jsv))
+ return json.jsPropertiesToValues(mklist(), jsv, reverse(range(0, jsv.length)));
+ if (typeof jsv == 'object')
+ return json.jsPropertiesToValues(mklist(), jsv, reverse(properties(jsv)));
+ if (typeof jsv == 'string')
+ return '' + jsv;
+ return jsv;
+}
+
+/**
+ * Return true if a list of strings contains a JSON document.
+ */
+json.isJSON = function(l) {
+ if (isNil(l))
+ return false;
+ var s = car(l).slice(0, 1);
+ return s == "[" || s == "{";
+};
+
+/**
+ * Convert a list of strings representing a JSON document to a list of values.
+ */
+json.readJSON = function(l) {
+ var s = writeStrings(l);
+ var obj;
+ eval('obj = { \"val\": ' + s + " }");
+ return json.jsValToValue(obj.val);
+};
+
+/**
+ * Convert a list of values to JSON array elements.
+ */
+json.valuesToJSElements = function(a, l, i) {
+ if (isNil(l))
+ return a;
+ var pv = json.valueToJSVal(car(l));
+ a[i] = pv
+ return json.valuesToJSElements(a, cdr(l), i + 1);
+};
+
+/**
+ * Convert a value to a JSON value.
+ */
+json.valueToJSVal = function(v) {
+ if (!isList(v))
+ return v;
+ if (json.isJSArray(v))
+ return json.valuesToJSElements(range(0, v.length), v, 0);
+ return json.valuesToJSProperties({}, v);
+};
+
+/**
+ * Convert a list of values to JSON properties.
+ */
+json.valuesToJSProperties = function(o, l) {
+ if (isNil(l))
+ return o;
+ var token = car(l);
+ if (isTaggedList(token, attribute)) {
+ var pv = json.valueToJSVal(attributeValue(token));
+ o['@' + attributeName(token).slice(1)] = pv;
+ } else if (isTaggedList(token, element)) {
+ if (elementHasValue(token)) {
+ var pv = json.valueToJSVal(elementValue(token));
+ o[elementName(token).slice(1)] = pv;
+ } else {
+ var child = {};
+ o[elementName(token).slice(1)] = child;
+ json.valuesToJSProperties(child, elementChildren(token));
+ }
+ }
+ return json.valuesToJSProperties(o, cdr(l));
+};
+
+/**
+ * Convert a list of values to a list of strings representing a JSON document.
+ */
+json.writeJSON = function(l) {
+ var jsv;
+ if (json.isJSArray(l))
+ jsv = json.valuesToJSElements(range(0, l.length), l, 0);
+ else
+ jsv = json.valuesToJSProperties({}, l);
+ var s = json.toJSON(jsv);
+ return mklist(s);
+}
+
+/**
+ * Convert a list + params to a JSON-RPC request.
+ */
+json.jsonRequest = function(id, func, params) {
+ var r = mklist(mklist("'id", id), mklist("'method", func), mklist("'params", params));
+ return json.writeJSON(valuesToElements(r));
+};
+
+/**
+ * Convert a value to a JSON-RPC result.
+ */
+json.jsonResult = function(id, val) {
+ return json.writeJSON(valuesToElements(mklist(mklist("'id", id), mklist("'result", val))));
+};
+
+/**
+ * Convert a JSON-RPC result to a value.
+ */
+json.jsonResultValue = function(s) {
+ var jsres = json.readJSON(s);
+ var res = elementsToValues(jsres);
+ var val = cadr(assoc("'result", res));
+ if (isList(val) && !json.isJSArray(val))
+ return mklist(val);
+ return val;
+};
+
+/**
+ * Escape a character.
+ */
+json.escapeJSONChar = function(c) {
+ if(c == "\"" || c == "\\") return "\\" + c;
+ if (c == "\b") return "\\b";
+ if (c == "\f") return "\\f";
+ if (c == "\n") return "\\n";
+ if (c == "\r") return "\\r";
+ if (c == "\t") return "\\t";
+ var hex = c.charCodeAt(0).toString(16);
+ if(hex.length == 1) return "\\u000" + hex;
+ if(hex.length == 2) return "\\u00" + hex;
+ if(hex.length == 3) return "\\u0" + hex;
+ return "\\u" + hex;
+};
+
+/**
+ * Encode a string into JSON format.
+ */
+json.escapeJSONString = function(s) {
+ // The following should suffice but Safari's regex is broken (doesn't support callback substitutions)
+ // return "\"" + s.replace(/([^\u0020-\u007f]|[\\\"])/g, json.escapeJSONChar) + "\"";
+
+ // Rather inefficient way to do it
+ var parts = s.split("");
+ for(var i = 0; i < parts.length; i++) {
+ var c = parts[i];
+ if(c == '"' || c == '\\' || c.charCodeAt(0) < 32 || c.charCodeAt(0) >= 128)
+ parts[i] = json.escapeJSONChar(parts[i]);
+ }
+ return "\"" + parts.join("") + "\"";
+};
+
+/**
+ * Marshall objects to JSON format.
+ */
+json.toJSON = function(o) {
+ if(o == null)
+ return "null";
+ if(o.constructor == String)
+ return json.escapeJSONString(o);
+ if(o.constructor == Number)
+ return o.toString();
+ if(o.constructor == Boolean)
+ return o.toString();
+ if(o.constructor == Date)
+ return '{javaClass: "java.util.Date", time: ' + o.valueOf() +'}';
+ if(o.constructor == Array) {
+ var v = [];
+ for(var i = 0; i < o.length; i++)
+ v.push(json.toJSON(o[i]));
+ return "[" + v.join(", ") + "]";
+ }
+ var v = [];
+ for(attr in o) {
+ if(o[attr] == null)
+ v.push("\"" + attr + "\": null");
+ else if(typeof o[attr] == "function")
+ ; // Skip
+ else
+ v.push(json.escapeJSONString(attr) + ": " + json.toJSON(o[attr]));
+ }
+ return "{" + v.join(", ") + "}";
+};
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/scdl.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/scdl.js
new file mode 100644
index 0000000000..b2e1464b9e
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/scdl.js
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * SCDL parsing functions.
+ */
+var scdl = {};
+
+/**
+ * Returns a composite element.
+ */
+scdl.composite = function(l) {
+ var cs = namedElementChildren("'composite", l);
+ if (isNil(cs))
+ return cs;
+ return car(cs);
+};
+
+/**
+ * Returns a list of components in a composite.
+ */
+scdl.components = function(l) {
+ var cs = namedElementChildren("'composite", l);
+ if (isNil(cs))
+ return cs;
+ return namedElementChildren("'component", car(cs));
+};
+
+/**
+ * Returns a list of service promotions in a composite.
+ */
+scdl.promotions = function(l) {
+ var cs = namedElementChildren("'composite", l);
+ if (isNil(cs))
+ return cs;
+ return namedElementChildren("'service", car(cs));
+};
+
+/**
+ * Returns the target of a service promotion.
+ */
+scdl.promote = function(l) {
+ var puri = namedAttributeValue("'promote", l);
+ if (isNil(puri))
+ return puri;
+ return car(tokens(puri));
+};
+
+/**
+ * Returns the name of a component, componentType, service or reference.
+ */
+scdl.name = function(l) {
+ return namedAttributeValue("'name", l);
+};
+
+/**
+ * Returns the description of a component, componentType, service or reference.
+ */
+scdl.documentation = function(l) {
+ var d = namedElementChildren("'documentation", l);
+ if (isNil(d))
+ return null;
+ if (!elementHasValue(car(d)))
+ return null;
+ var v = elementValue(car(d));
+ return v;
+};
+
+/**
+ * Returns the title of a component or componentType.
+ */
+scdl.title = function(l) {
+ return namedAttributeValue("'t:title", l);
+};
+
+/**
+ * Returns the color of a component or componentType.
+ */
+scdl.color = function(l) {
+ return namedAttributeValue("'t:color", l);
+};
+
+/**
+ * Returns the x position of a component.
+ */
+scdl.x = function(l) {
+ return namedAttributeValue("'t:x", l);
+};
+
+/**
+ * Returns the y position of a component.
+ */
+scdl.y = function(l) {
+ return namedAttributeValue("'t:y", l);
+};
+
+/**
+ * Returns the implementation of a component.
+ */
+scdl.implementation = function(l) {
+ function filterImplementation(v) {
+ return isElement(v) && cadr(v).match("implementation.") != null;
+ }
+
+ var n = filter(filterImplementation, l);
+ if (isNil(n))
+ return null;
+ return car(n);
+};
+
+/**
+ * Returns the type of a component or componentType implementation.
+ */
+scdl.implementationType = function(l) {
+ return elementName(l).substring(1);
+};
+
+/**
+ * Returns the URI of a service, reference or implementation.
+ */
+scdl.uri = function(l) {
+ return namedAttributeValue("'uri", l);
+};
+
+/**
+ * Returns the align attribute of a service or reference.
+ */
+scdl.align = function(l) {
+ return namedAttributeValue("'t:align", l);
+};
+
+/**
+ * Returns the visible attribute of a service or reference.
+ */
+scdl.visible = function(l) {
+ return namedAttributeValue("'t:visible", l);
+};
+
+/**
+ * Returns the clonable attribute of a reference.
+ */
+scdl.clonable = function(l) {
+ return namedAttributeValue("'t:clonable", l);
+};
+
+/**
+ * Returns a list of services in a component or componentType.
+ */
+scdl.services = function(l) {
+ return namedElementChildren("'service", l);
+};
+
+/**
+ * Returns a list of references in a component or componentType.
+ */
+scdl.references = function(l) {
+ return namedElementChildren("'reference", l);
+};
+
+/**
+ * Returns a list of bindings in a service or reference.
+ */
+scdl.bindings = function(l) {
+ function filterBinding(v) {
+ return isElement(v) && cadr(v).match("binding.") != null;
+ }
+
+ return filter(filterBinding, l);
+};
+
+/**
+ * Returns the type of a binding.
+ */
+scdl.bindingType = function(l) {
+ return elementName(l).substring(1);
+};
+
+/**
+ * Returns the target of a reference.
+ */
+scdl.target = function(l) {
+ function targetURI() {
+ function bindingsTarget(l) {
+ if (isNil(l))
+ return null;
+ var u = scdl.uri(car(l));
+ if (!isNil(u))
+ return u;
+ return bindingsTarget(cdr(l));
+ }
+
+ var t = namedAttributeValue("'target", l);
+ if (!isNil(t))
+ return t;
+ return bindingsTarget(scdl.bindings(l));
+ }
+ var turi = targetURI();
+ if (isNil(turi))
+ return turi;
+ return car(tokens(turi));
+};
+
+/**
+ * Returns a list of properties in a component or componentType.
+ */
+scdl.properties = function(l) {
+ return namedElementChildren("'property", l);
+};
+
+/**
+ * Returns the value of a property.
+ */
+scdl.propertyValue = function(l) {
+ if (!elementHasValue(l))
+ return '';
+ return elementValue(l);
+};
+
+/**
+ * Convert a list of elements to a name -> element assoc list.
+ */
+scdl.nameToElementAssoc = function(l) {
+ if (isNil(l))
+ return l;
+ return cons(mklist(scdl.name(car(l)), car(l)), scdl.nameToElementAssoc(cdr(l)));
+};
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.css b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.css
new file mode 100644
index 0000000000..9b960e95ca
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.css
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+white-space: margin: 0px;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+.delayed {
+visibility: hidden;
+}
+
+table {
+border: 0px; border-collapse: collapse; border-color: #a2bae7; border-style: solid;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+overflow: visible;
+}
+
+.trb {
+border-bottom: 1px; border-bottom-style: solid; border-color: #dcdcdc;
+}
+
+th {
+font-weight: bold; background-color: #e5ecf9; color: #000000;
+text-align: left; padding-left: 2px; padding-right: 8px; padding-top: 2px; padding-bottom: 4px; vertical-align: text-top; white-space: nowrap;
+border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #a2bae7; border-right-color: #a2bae7;
+overflow: hidden;
+}
+
+.section {
+font-weight: bold; background-color: #e5ecf9; color: #000000;
+text-align: left; padding-left: 2px; padding-right: 8px; padding-top: 2px; padding-bottom: 4px; vertical-align: text-top; white-space: nowrap;
+border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #a2bae7; border-right-color: #a2bae7;
+overflow: hidden;
+}
+
+.thl {
+border-left: 0px;
+}
+
+.thr {
+border-right: 0px;
+}
+
+.ths {
+padding: 0px;
+}
+
+td {
+padding-left: 2px; padding-top: 2px; padding-right: 8px; white-space: nowrap; vertical-align: text-top; border: 0px;
+}
+
+.tdl {
+border-right: 1px; border-style: solid; border-color: #a2bae7; width: 10px;
+}
+
+.tdr {
+border-left: 1px; border-style: solid; border-color: #a2bae7;
+}
+
+.tdw {
+padding-left: 2px; padding-top: 2px; padding-right: 8px; white-space: normal; vertical-align: text-top;
+}
+
+.datatdl {
+border-right: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc; width: 10px;
+}
+
+.datatdr {
+border-left: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc;
+}
+
+.datatable {
+border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc;
+overflow: visible;
+}
+
+.databg {
+opacity: .6;
+-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
+filter: alpha(opacity=60);
+}
+
+.widgetframe {
+visibility: hidden; width: 0px; height: 0px; border: 0px;
+}
+
+.loadedframe {
+width: 100%; height: 100%; border: 0px;
+margin: 0px; padding: 0px;
+}
+
+input {
+vertical-align: middle;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+-webkit-text-size-adjust: 140%;
+}
+
+textarea {
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+a:link {
+color: #598edd;
+text-decoration: none
+}
+
+a:visited {
+color: #598edd;
+text-decoration: none
+}
+
+.amenu {
+color: #598edd;
+text-decoration: none
+}
+
+.smenu {
+font-weight: bold;
+color: #000000;
+text-decoration: none
+}
+
+h1 {
+font-size: 200%; font-weight: bold;
+vertical-align: middle;
+margin: 0px;
+}
+
+h2 {
+font-size: 150%; font-weight: bold;
+vertical-align: middle;
+margin: 0px;
+}
+
+.hd1 {
+font-size: 200%; font-weight: bold;
+}
+
+.hd2 {
+font-size: 150%; font-weight: bold;
+}
+
+img {
+border: 0px;
+}
+
+.imgbutton {
+width: 142px; height: 64px; margin-left: 20px; margin-right: 20px; padding: 0px; border: 1px;
+cursor: pointer; cursor: hand;
+}
+
+.tbar {
+margin: 0px;
+padding-top: 0px; padding-left: 0px; padding-right: 0px; padding-bottom: 3px;
+border-bottom: 1px solid #a2bae7; border-collapse: separate;
+}
+
+.ltbar {
+padding-left: 0px; padding-top: 0px; padding-right: 8px; white-space: nowrap; vertical-align: top;
+}
+
+.rtbar {
+padding-left: 8px; padding-right: 0px; padding-top: 0px; white-space: nowrap; vertical-align: top;
+text-align: right;
+}
+
+.suggest {
+background-color: #e5ecf9; color: #598edd;
+border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4;
+border-left-color: #d1d3d4; border-right-color: #d1d3d4;
+position: absolute;
+overflow: auto; overflow-x: hidden;
+cursor: default;
+padding: 0px; margin: 0px;
+}
+
+.suggestTable {
+border: 0px; border-collapse: separate;
+padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-bottom: 2px;
+margin: 0px;
+}
+
+.suggestItem {
+padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: text-top;
+background-color: #e5ecf9; color: #598edd;
+}
+
+.suggestHilighted {
+padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: text-top;
+background-color: #598edd; color: #e5ecf9;
+}
+
+v\: * {
+behavior:url(#default#VML);
+display:inline-block;
+}
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.js
new file mode 100644
index 0000000000..db8c439de4
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/ui.js
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * UI utility functions.
+ */
+
+var ui = {};
+
+/**
+ * Return true if the current browser is Internet Explorer.
+ */
+ui.isIE = function() {
+ if (typeof ui.isIE.detected != 'undefined')
+ return ui.isIE.detected;
+ ui.isIE.detected = navigator.appName == 'Microsoft Internet Explorer';
+ return ui.isIE.detected;
+};
+
+/**
+ * Build a menu bar.
+ */
+ui.menu = function(name, href) {
+ function Menu(n, h) {
+ this.name = n;
+ this.href = h;
+
+ this.content = function() {
+ function complete(uri) {
+ var q = uri.indexOf('?');
+ if (q != -1)
+ return complete(uri.substr(0, q));
+ if (uri.match('.*\.html$'))
+ return uri;
+ if (uri.match('.*/$'))
+ return uri + 'index.html';
+ return uri + '/index.html';
+ }
+
+ if (complete(this.href) != complete(window.top.location.pathname))
+ return '<a href="' + this.href + '" target="_parent"><span class=amenu>' + this.name + '</span></a>';
+ return '<a href="' + this.href + '" target="_parent"><span class=smenu>' + this.name + '</span></a>';
+ };
+ }
+ return new Menu(name, href);
+};
+
+ui.menubar = function(left, right) {
+ var bar = '<table cellpadding="0" cellspacing="0" width="100%" class=tbar><tr>' +
+ '<td class=ltbar><table border="0" cellspacing="0" cellpadding="0"><tr>';
+ for (i in left)
+ bar = bar + '<td class=ltbar>' + left[i].content() + '</td>'
+
+ bar = bar + '</tr></table></td>' +
+ '<td class=rtbar><table border="0" cellpadding="0" cellspacing="0" align="right"><tr>';
+ for (i in right)
+ bar = bar + '<td class=rtbar>' + right[i].content() + '</td>'
+
+ bar = bar + '</tr></table></td></tr></table>';
+ return bar;
+};
+
+/**
+ * Autocomplete / suggest support for input fields
+ * To use it declare a 'suggest' function as follows:
+ * function suggestItems() {
+ * return new Array('abc', 'def', 'ghi');
+ * }
+ * then hook it to an input field as follows:
+ * suggest(document.yourForm.yourInputField, suggestItems);
+ */
+ui.selectSuggestion = function(node, value) {
+ for (;;) {
+ node = node.parentNode;
+ if (node.tagName.toLowerCase() == 'div')
+ break;
+ }
+ node.selectSuggestion(value);
+};
+
+ui.hilightSuggestion = function(node, over) {
+ if (over)
+ node.className = 'suggestHilighted';
+ node.className = 'suggestItem';
+};
+
+ui.suggest = function(input, suggestFunction) {
+ input.suggest = suggestFunction;
+
+ input.selectSuggestion = function(value) {
+ this.hideSuggestDiv();
+ this.value = value;
+ }
+
+ input.hideSuggestDiv = function() {
+ if (this.suggestDiv != null) {
+ this.suggestDiv.style.visibility = 'hidden';
+ }
+ }
+
+ input.showSuggestDiv = function() {
+ if (this.suggestDiv == null) {
+ this.suggestDiv = document.createElement('div');
+ this.suggestDiv.input = this;
+ this.suggestDiv.className = 'suggest';
+ input.parentNode.insertBefore(this.suggestDiv, input);
+ this.suggestDiv.style.visibility = 'hidden';
+ this.suggestDiv.style.zIndex = '99';
+
+ this.suggestDiv.selectSuggestion = function(value) {
+ this.input.selectSuggestion(value);
+ }
+ }
+
+ var values = this.suggest();
+ var items = '';
+ for (var i = 0; i < values.length; i++) {
+ if (values[i].indexOf(this.value) == -1)
+ continue;
+ if (items.length == 0)
+ items += '<table class=suggestTable>';
+ items += '<tr><td class="suggestItem" ' +
+ 'onmouseover="ui.hilightSuggestion(this, true)" onmouseout="ui.hilightSuggestion(this, false)" ' +
+ 'onmousedown="ui.selectSuggestion(this, \'' + values[i] + '\')">' + values[i] + '</td></tr>';
+ }
+ if (items.length != 0)
+ items += '</table>';
+ this.suggestDiv.innerHTML = items;
+
+ if (items.length != 0) {
+ var node = input;
+ var left = 0;
+ var top = 0;
+ for (;;) {
+ left += node.offsetLeft;
+ top += node.offsetTop;
+ node = node.offsetParent;
+ if (node.tagName.toLowerCase() == 'body')
+ break;
+ }
+ this.suggestDiv.style.left = left;
+ this.suggestDiv.style.top = top + input.offsetHeight;
+ this.suggestDiv.style.visibility = 'visible';
+ } else
+ this.suggestDiv.style.visibility = 'hidden';
+ }
+
+ input.onkeydown = function(event) {
+ this.showSuggestDiv();
+ };
+
+ input.onkeyup = function(event) {
+ this.showSuggestDiv();
+ };
+
+ input.onmousedown = function(event) {
+ this.showSuggestDiv();
+ };
+
+ input.onblur = function(event) {
+ setTimeout(function() { input.hideSuggestDiv(); }, 50);
+ };
+};
+
+/**
+ * Return the content document of a window.
+ */
+ui.content = function(win) {
+ if (!isNil(win.document))
+ return win.document;
+ if (!isNil(win.contentDocument))
+ return win.contentDocument;
+ return null;
+};
+
+/**
+ * Return a child element of a node with the given id.
+ */
+ui.elementByID = function(node, id) {
+ for (var i in node.childNodes) {
+ var child = node.childNodes[i];
+ if (child.id == id)
+ return child;
+ var gchild = ui.elementByID(child, id);
+ if (gchild != null)
+ return gchild;
+ }
+ return null;
+};
+
+/**
+ * Return the current document, or a child element with the given id.
+ */
+function $(id) {
+ if (id == document) {
+ if (!isNil(document.widget))
+ return document.widget;
+ return document;
+ }
+ return ui.elementByID($(document), id);
+};
+
+/**
+ * Return a dictionary of the query parameters.
+ */
+ui.queryParams = function() {
+ var qp = new Array();
+ var qs = window.location.search.substring(1).split('&');
+ for (var i = 0; i < qs.length; i++) {
+ var e = qs[i].indexOf('=');
+ if (e > 0)
+ qp[qs[i].substring(0, e)] = unescape(qs[i].substring(e + 1));
+ }
+ return qp;
+}
+
+/**
+ * Bind a widget iframe to an element.
+ */
+ui.widgets = {};
+ui.onload = {};
+
+ui.loadwidget = function(el, doc, cb) {
+ var f = el + 'Frame';
+ window.ui.widgets[f] = el;
+ window.ui.onload[f] = cb;
+ var div = document.createElement('div');
+ div.id = f + 'Div';
+ div.innerHTML = '<iframe id="' + f + '" class="widgetframe" scrolling="no" frameborder="0" src="' + doc + '" onload="window.ui.onload[this.id]()"></iframe>';
+ document.body.appendChild(div);
+ return f;
+};
+
+/**
+ * Show the current document body.
+ */
+ui.showbody = function() {
+ document.body.style.visibility = 'visible';
+};
+
+/**
+ * Install a widget into the element bound to its iframe.
+ */
+ui.installwidget = function() {
+ if (isNil(window.parent) || isNil(window.parent.ui) || isNil(window.parent.ui.widgets))
+ return true;
+ var pdoc = ui.content(window.parent);
+ for (w in window.parent.ui.widgets) {
+ var ww = ui.elementByID(pdoc, w).contentWindow;
+ if (ww == window) {
+ document.widget = ui.elementByID(pdoc, window.parent.ui.widgets[w]);
+ document.widget.innerHTML = document.body.innerHTML;
+ return true;
+ }
+ }
+ return true;
+};
+
+/**
+ * Load an iframe into an element.
+ */
+ui.loadiframe = function(el, doc) {
+ var f = el + 'Frame';
+ $(el).innerHTML =
+ '<iframe id="' + f + '" class="loadedframe" scrolling="no" frameborder="0" src="' + doc + '"></iframe>';
+ return f;
+};
+
+/**
+ * Convert a CSS position to a numeric position.
+ */
+ui.csspos = function(p) {
+ if (p == '')
+ return 0;
+ return Number(p.substr(0, p.length - 2));
+};
+
+/**
+ * Convert a list of elements to an HTML table.
+ */
+ui.datatable = function(l) {
+ log('datatable', writeValue(l));
+
+ function indent(i) {
+ if (i == 0)
+ return '';
+ return '&nbsp;&nbsp;' + indent(i - 1);
+ }
+
+ function rows(l, i) {
+ if (isNil(l))
+ return '';
+ var e = car(l);
+
+ if (!isList(e))
+ return rows(expandElementValues("'value", l), i);
+
+ if (elementHasValue(e)) {
+ var v = elementValue(e);
+ if (!isList(v)) {
+ return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' +
+ '<td class="datatdr">' + v + '</td></tr>' +
+ rows(cdr(l), i);
+ }
+
+ return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i);
+ }
+
+ return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' +
+ '<td class="datatdr">' + '</td></tr>' +
+ rows(elementChildren(e), i + 1) +
+ rows(cdr(l), i);
+ }
+
+ return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>';
+}
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uiblue.css b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uiblue.css
new file mode 100644
index 0000000000..4efde86a9a
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uiblue.css
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+white-space: margin: 0px;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+table {
+border: 0px; border-collapse: collapse; border-color: #000000; border-style: solid;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+th {
+font-weight: bold; background-color: #3b5998; color: #ffffff;
+text-align: left; padding-left: 2px; padding-right: 8px; padding-top: 2px; padding-bottom: 4px; vertical-align: text-top;
+border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px;
+border-style: solid; border-top-color: #000000; border-bottom-color: #000000;
+}
+
+.thl {
+border-left: 0px;
+}
+
+.thr {
+border-right: 0px;
+}
+
+.ths {
+padding: 0px;
+}
+
+td {
+padding-left: 2px; padding-top: 2px; padding-right: 8px; white-space: nowrap; vertical-align: text-top;
+}
+
+.tdl {
+border-right: 1px;
+}
+
+.tdr {
+border-left: 1px;
+}
+
+.tdw {
+padding-left: 2px; padding-top: 2px; padding-right: 8px; white-space: normal; vertical-align: text-top;
+}
+
+.widgetframe {
+visibility: hidden; width: 0px; height: 0px; border: 0px;
+}
+
+.loadedframe {
+width: 100%; height: 100%; border: 0px;
+margin: 0px; padding: 0px;
+}
+
+input {
+vertical-align: middle;
+-webkit-text-size-adjust: 140%;
+}
+
+textarea {
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+a:link {
+color: #3b5998;
+}
+
+a:visited {
+color: #3b5998;
+}
+
+.amenu {
+font-weight: bold;
+color: #ffffff;
+}
+
+.smenu {
+font-weight: bold;
+color: #ffffff;
+}
+
+h1 {
+font-size: 200%; font-weight: bold;
+vertical-align: middle;
+margin: 0px;
+}
+
+h2 {
+font-size: 150%; font-weight: bold;
+vertical-align: middle;
+margin: 0px;
+}
+
+.hd1 {
+font-size: 200%; font-weight: bold;
+}
+
+.hd2 {
+font-size: 150%; font-weight: bold;
+}
+
+.imgbutton {
+width: 142px; height: 64px; margin-left: 20px; margin-right: 20px; padding: 0px; border: 1px;
+cursor: pointer; cursor: hand;
+}
+
+.tbar {
+font-weight: bold; background-color: #3b5998; color: #ffffff;
+margin: 0px;
+padding-top: 4px; padding-left: 2px; padding-right: 8px; padding-bottom: 8px; vertical-align: text-top;
+border-bottom: 1px solid #000000; border-collapse: separate;
+}
+
+.ltbar {
+padding-left: 0px; padding-top: 0px; padding-right: 16px; white-space: nowrap; vertical-align: top;
+}
+
+.rtbar {
+padding-left: 8px; padding-right: 0px; padding-top: 0px; white-space: nowrap; vertical-align: top;
+text-align: right;
+}
+
+.suggest {
+background-color: #e5ecf9; color: #598edd;
+border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4;
+border-left-color: #d1d3d4; border-right-color: #d1d3d4;
+position: absolute;
+overflow: auto; overflow-x: hidden;
+cursor: default;
+padding: 0px; margin: 0px;
+}
+
+.suggestTable {
+border: 0px; border-collapse: separate;
+padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-bottom: 2px;
+margin: 0px;
+}
+
+.suggestItem {
+padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: text-top;
+background-color: #e5ecf9; color: #598edd;
+}
+
+.suggestHilighted {
+padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: text-top;
+background-color: #598edd; color: #e5ecf9;
+}
+
+v\: * {
+behavior:url(#default#VML);
+display:inline-block;
+}
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uicyan.css b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uicyan.css
new file mode 100644
index 0000000000..9b960e95ca
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/uicyan.css
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+white-space: margin: 0px;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+.delayed {
+visibility: hidden;
+}
+
+table {
+border: 0px; border-collapse: collapse; border-color: #a2bae7; border-style: solid;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+overflow: visible;
+}
+
+.trb {
+border-bottom: 1px; border-bottom-style: solid; border-color: #dcdcdc;
+}
+
+th {
+font-weight: bold; background-color: #e5ecf9; color: #000000;
+text-align: left; padding-left: 2px; padding-right: 8px; padding-top: 2px; padding-bottom: 4px; vertical-align: text-top; white-space: nowrap;
+border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #a2bae7; border-right-color: #a2bae7;
+overflow: hidden;
+}
+
+.section {
+font-weight: bold; background-color: #e5ecf9; color: #000000;
+text-align: left; padding-left: 2px; padding-right: 8px; padding-top: 2px; padding-bottom: 4px; vertical-align: text-top; white-space: nowrap;
+border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #a2bae7; border-right-color: #a2bae7;
+overflow: hidden;
+}
+
+.thl {
+border-left: 0px;
+}
+
+.thr {
+border-right: 0px;
+}
+
+.ths {
+padding: 0px;
+}
+
+td {
+padding-left: 2px; padding-top: 2px; padding-right: 8px; white-space: nowrap; vertical-align: text-top; border: 0px;
+}
+
+.tdl {
+border-right: 1px; border-style: solid; border-color: #a2bae7; width: 10px;
+}
+
+.tdr {
+border-left: 1px; border-style: solid; border-color: #a2bae7;
+}
+
+.tdw {
+padding-left: 2px; padding-top: 2px; padding-right: 8px; white-space: normal; vertical-align: text-top;
+}
+
+.datatdl {
+border-right: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc; width: 10px;
+}
+
+.datatdr {
+border-left: 1px; border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc;
+}
+
+.datatable {
+border-top: 1px; border-bottom: 1px; border-style: solid; border-color: #dcdcdc;
+overflow: visible;
+}
+
+.databg {
+opacity: .6;
+-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
+filter: alpha(opacity=60);
+}
+
+.widgetframe {
+visibility: hidden; width: 0px; height: 0px; border: 0px;
+}
+
+.loadedframe {
+width: 100%; height: 100%; border: 0px;
+margin: 0px; padding: 0px;
+}
+
+input {
+vertical-align: middle;
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+-webkit-text-size-adjust: 140%;
+}
+
+textarea {
+font-family: arial,sans-serif; font-style: normal; font-variant: normal; font-size: 13px;
+}
+
+a:link {
+color: #598edd;
+text-decoration: none
+}
+
+a:visited {
+color: #598edd;
+text-decoration: none
+}
+
+.amenu {
+color: #598edd;
+text-decoration: none
+}
+
+.smenu {
+font-weight: bold;
+color: #000000;
+text-decoration: none
+}
+
+h1 {
+font-size: 200%; font-weight: bold;
+vertical-align: middle;
+margin: 0px;
+}
+
+h2 {
+font-size: 150%; font-weight: bold;
+vertical-align: middle;
+margin: 0px;
+}
+
+.hd1 {
+font-size: 200%; font-weight: bold;
+}
+
+.hd2 {
+font-size: 150%; font-weight: bold;
+}
+
+img {
+border: 0px;
+}
+
+.imgbutton {
+width: 142px; height: 64px; margin-left: 20px; margin-right: 20px; padding: 0px; border: 1px;
+cursor: pointer; cursor: hand;
+}
+
+.tbar {
+margin: 0px;
+padding-top: 0px; padding-left: 0px; padding-right: 0px; padding-bottom: 3px;
+border-bottom: 1px solid #a2bae7; border-collapse: separate;
+}
+
+.ltbar {
+padding-left: 0px; padding-top: 0px; padding-right: 8px; white-space: nowrap; vertical-align: top;
+}
+
+.rtbar {
+padding-left: 8px; padding-right: 0px; padding-top: 0px; white-space: nowrap; vertical-align: top;
+text-align: right;
+}
+
+.suggest {
+background-color: #e5ecf9; color: #598edd;
+border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px;
+border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4;
+border-left-color: #d1d3d4; border-right-color: #d1d3d4;
+position: absolute;
+overflow: auto; overflow-x: hidden;
+cursor: default;
+padding: 0px; margin: 0px;
+}
+
+.suggestTable {
+border: 0px; border-collapse: separate;
+padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-bottom: 2px;
+margin: 0px;
+}
+
+.suggestItem {
+padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: text-top;
+background-color: #e5ecf9; color: #598edd;
+}
+
+.suggestHilighted {
+padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: text-top;
+background-color: #598edd; color: #e5ecf9;
+}
+
+v\: * {
+behavior:url(#default#VML);
+display:inline-block;
+}
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/util.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/util.js
new file mode 100644
index 0000000000..5697ad27d4
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/util.js
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Simple utility functions.
+ */
+
+/**
+ * Scheme-like lists.
+ */
+function cons(car, cdr) {
+ var a = new Array();
+ a.push(car);
+ return a.concat(cdr);
+}
+
+function car(l) {
+ return l[0];
+}
+
+function first(l) {
+ return car(l);
+}
+
+function cdr(l) {
+ return l.slice(1);
+}
+
+function rest(l) {
+ return cdr(l);
+}
+
+function cadr(l) {
+ return car(cdr(l));
+}
+
+function cddr(l) {
+ return cdr(cdr(l));
+}
+
+function caddr(l) {
+ return car(cddr(l));
+}
+
+function cdddr(l) {
+ return cdr(cdr(cdr(l)));
+}
+
+function cadddr(l) {
+ return car(cdddr(l));
+}
+
+function append(a, b) {
+ return a.concat(b);
+}
+
+function reverse(l) {
+ return l.slice(0).reverse();
+}
+
+function range(a, b) {
+ var l = new Array();
+ for (var x = a; x < b; x++)
+ l.push(x);
+ return l;
+}
+
+function isNil(v) {
+ if (v == null || typeof v == 'undefined' || (v.constructor == Array && v.length == 0))
+ return true;
+ return false;
+}
+
+function isSymbol(v) {
+ if (typeof v == 'string' && v.slice(0, 1) == "'")
+ return true;
+ return false;
+}
+
+function isString(v) {
+ if (typeof v == 'string' && v.slice(0, 1) != "'")
+ return true;
+ return false;
+}
+
+function isList(v) {
+ if (v != null && typeof v != 'undefined' && v.constructor == Array)
+ return true;
+ return false;
+}
+
+function isTaggedList(v, t) {
+ if (isList(v) && !isNil(v) && car(v) == t)
+ return true;
+ return false;
+}
+
+var emptylist = new Array();
+
+function mklist() {
+ if (arguments.length == 0)
+ return emptylist;
+ var a = new Array();
+ for (i = 0; i < arguments.length; i++)
+ a[i] = arguments[i];
+ return a;
+}
+
+function length(l) {
+ return l.length;
+}
+
+/**
+ * Scheme-like associations.
+ */
+function assoc(k, l) {
+ if (isNil(l))
+ return mklist();
+ if (k == car(car(l)))
+ return car(l);
+ return assoc(k, cdr(l));
+}
+
+/**
+ * Map, filter and reduce functions.
+ */
+function map(f, l) {
+ if (isNil(l))
+ return l;
+ return cons(f(car(l)), map(f, cdr(l)));
+}
+
+function filter(f, l) {
+ if (isNil(l))
+ return l;
+ if (f(car(l)))
+ return cons(car(l), filter(f, cdr(l)));
+ return filter(f, cdr(l));
+}
+
+function reduce(f, i, l) {
+ if (isNil(l))
+ return i;
+ return reduce(f, f(i, car(l)), cdr(l));
+}
+
+/**
+ * Split a path into a list of segments.
+ */
+function tokens(path) {
+ return filter(function(s) { return length(s) != 0; }, path.split("/"));
+}
+
+/**
+ * Log a value.
+ */
+var rconsole;
+
+function log(v) {
+ try {
+ var s = '';
+ for (i = 0; i < arguments.length; i++) {
+ s = s + writeValue(arguments[i]);
+ if (i < arguments.length)
+ s = s + ' ';
+ }
+
+ if (rconsole) {
+ try {
+ rconsole.log(s);
+ } catch (e) {}
+ }
+ try {
+ console.log(s);
+ } catch (e) {}
+ } catch (e) {}
+ return true;
+}
+
+/**
+ * Dump an object to the debug console.
+ */
+function debug(o) {
+ try {
+ for (f in o) {
+ try {
+ log('debug ' + f + '=' + o[f]);
+ } catch (e) {}
+ }
+ } catch (e) {}
+ return true;
+}
+
+/**
+ * Simple assert function.
+ */
+function AssertException() {
+}
+
+AssertException.prototype.toString = function () {
+ return 'AssertException';
+};
+
+function assert(exp) {
+ if (!exp)
+ throw new AssertException();
+}
+
+/**
+ * Write a list of strings.
+ */
+function writeStrings(l) {
+ if (isNil(l))
+ return '';
+ return car(l) + writeStrings(cdr(l));
+}
+
+/**
+ * Write a value using a Scheme-like syntax.
+ */
+function writeValue(v) {
+ function writePrimitive(p) {
+ if (isSymbol(p))
+ return '' + p.substring(1);
+ if (isString(p))
+ return '"' + p + '"';
+ return '' + p;
+ }
+
+ function writeList(l) {
+ if (isNil(l))
+ return '';
+ return ' ' + writeValue(car(l)) + writeList(cdr(l));
+ }
+
+ if (!isList(v))
+ return writePrimitive(v);
+ if (isNil(v))
+ return '()';
+ return '(' + writeValue(car(v)) + writeList(cdr(v)) + ')';
+}
+
+/**
+ * Apply a function and memoize its result.
+ */
+function memo(obj, key, f) {
+ if (!obj[memo])
+ obj.memo = {};
+ if (obj.memo[key])
+ return obj.memo[key];
+ return obj.memo[key] = f();
+}
+
+/**
+ * Un-memoize store results.
+ */
+function unmemo(obj) {
+ obj.memo = {};
+ return true;
+}
+
+/**
+ * Returns a list of the properties of an object.
+ */
+function properties(o) {
+ var a = new Array();
+ for (p in o)
+ a.push(p);
+ return a;
+}
+
+/**
+ * Functions with side effects. Use with moderation.
+ */
+
+/**
+ * Set the car of a list.
+ */
+function setcar(l, v) {
+ l[0] = v;
+ return l;
+}
+
+/**
+ * Set the cadr of a list.
+ */
+function setcadr(l, v) {
+ l[1] = v;
+ return l;
+}
+
+/**
+ * Set the caddr of a list.
+ */
+function setcaddr(l, v) {
+ l[2] = v;
+ return l;
+}
+
+/**
+ * Append the elements of a list to a list.
+ */
+function setappend(a, b) {
+ if (isNil(b))
+ return a;
+ a.push(car(b));
+ return setappend(a, cdr(b));
+}
+
+/**
+ * Set the cdr of a list.
+ */
+function setcdr(a, b) {
+ a.length = 1;
+ return setappend(a, b);
+}
+
+/**
+ * Set the contents of a list.
+ */
+function setlist(a, b) {
+ if (b == a)
+ return b;
+ a.length = 0;
+ return setappend(a, b);
+}
+
diff --git a/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/xmlutil.js b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/xmlutil.js
new file mode 100644
index 0000000000..3965596599
--- /dev/null
+++ b/sca-java-2.x/trunk/modules/node-manager/src/test/resources/ui/xmlutil.js
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * XML handling functions.
+ */
+
+/**
+ * Convert a DOM node list to a regular list.
+ */
+function nodeList(n) {
+ var l = new Array();
+ if (isNil(n))
+ return l;
+ for (var i = 0; i < n.length; i++)
+ l[i] = n[i];
+ return l;
+}
+
+/**
+ * Append a list of nodes to a parent node.
+ */
+function appendNodes(nodes, p) {
+ if (isNil(nodes))
+ return p;
+ p.appendChild(car(nodes));
+ return appendNodes(cdr(nodes), p);
+};
+
+/**
+ * Return the child attributes of an element.
+ */
+function childAttributes(e) {
+ return filter(function(n) { return n.nodeType == 2; }, nodeList(e.attributes));
+}
+
+/**
+ * Return the child elements of an element.
+ */
+function childElements(e) {
+ return filter(function(n) { return n.nodeType == 1; }, nodeList(e.childNodes));
+}
+
+/**
+ * Return the child text nodes of an element.
+ */
+function childText(e) {
+ function trim(s) {
+ return s.replace(/^\s*/, '').replace(/\s*$/, '');
+ }
+ return filter(function(n) { return n.nodeType == 3 && trim(n.nodeValue) != ''; }, nodeList(e.childNodes));
+}
+
+/**
+ * Read a list of XML attributes.
+ */
+function readAttributes(p, a) {
+ if (isNil(a))
+ return a;
+ var x = car(a);
+ return cons(mklist(attribute, "'" + x.nodeName, x.nodeValue), readAttributes(p, cdr(a)));
+}
+
+/**
+ * Read an XML element.
+ */
+function readElement(e, childf) {
+ var l = append(append(mklist(element, "'" + e.nodeName), readAttributes(e, childf(e))), readElements(childElements(e), childf));
+ var t = childText(e);
+ if (isNil(t))
+ return l;
+ return append(l, mklist(car(t).nodeValue));
+}
+
+/**
+ * Read a list of XML elements.
+ */
+function readElements(l, childf) {
+ if (isNil(l))
+ return l;
+ return cons(readElement(car(l), childf), readElements(cdr(l), childf));
+}
+
+/**
+ * Return true if a list of strings contains an XML document.
+ */
+function isXML(l) {
+ if (isNil(l))
+ return false;
+ return car(l).substring(0, 5) == '<?xml';
+}
+
+/**
+ * Parse a list of strings representing an XML document.
+ */
+function parseXML(l) {
+ var s = writeStrings(l);
+ if (window.DOMParser) {
+ var p = new DOMParser();
+ return p.parseFromString(s, "text/xml");
+ }
+ var doc;
+ try {
+ doc = new ActiveXObject("MSXML2.DOMDocument");
+ } catch (e) {
+ doc = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ doc.async = 'false';
+ doc.loadXML(s);
+ return doc;
+}
+
+/**
+ * Read a list of values from an XML document.
+ */
+function readXMLDocument(doc) {
+ var root = childElements(doc);
+ if (isNil(root))
+ return mklist();
+ return mklist(readElement(car(root), childAttributes));
+}
+
+/**
+ * Read a list of values from an XHTML element.
+ */
+function readXHTMLElement(xhtml) {
+ // Special XHTML attribute filtering on IE
+ function ieChildAttributes(e) {
+ var a = filter(function(n) {
+ // Filter out empty and internal DOM attributes
+ if (n.nodeType != 2 || isNil(n.nodeValue) || n.nodeValue == '')
+ return false;
+ if (n.nodeName == 'contentEditable' || n.nodeName == 'maxLength' || n.nodeName == 'loop' || n.nodeName == 'start')
+ return false;
+ return true;
+ }, nodeList(e.attributes));
+
+ if (e.style.cssText == '')
+ return a;
+
+ // Add style attribute
+ var sa = new Object();
+ sa.nodeName = 'style';
+ sa.nodeValue = e.style.cssText;
+ return cons(sa, a);
+ }
+
+ var childf = (typeof(XMLSerializer) != 'undefined')? childAttributes : ieChildAttributes;
+ return mklist(readElement(xhtml, childf));
+}
+
+/**
+ * Read a list of values from a list of strings representing an XML document.
+ */
+function readXML(l) {
+ return readXMLDocument(parseXML(l));
+}
+
+/**
+ * Return a list of strings representing an XML document.
+ */
+function writeXMLDocument(doc) {
+ if (typeof(XMLSerializer) != 'undefined')
+ return mklist(new XMLSerializer().serializeToString(doc));
+ return mklist(doc.xml);
+}
+
+/**
+ * Write a list of XML element and attribute tokens.
+ */
+function expandElementValues(n, l) {
+ if (isNil(l))
+ return l;
+ return cons(cons(element, cons(n, car(l))), expandElementValues(n, cdr(l)));
+}
+
+function writeList(l, node, doc) {
+ if (isNil(l))
+ return node;
+
+ var token = car(l);
+ if (isTaggedList(token, attribute)) {
+ node.setAttribute(attributeName(token).substring(1), '' + attributeValue(token));
+
+ } else if (isTaggedList(token, element)) {
+
+ function mkelem(tok, doc) {
+ function xmlns(l) {
+ if (isNil(l))
+ return null;
+ var t = car(l);
+ if (isTaggedList(t, attribute)) {
+ if (attributeName(t).substring(1) == 'xmlns')
+ return attributeValue(t);
+ }
+ return xmlns(cdr(l));
+ }
+
+ var ns = xmlns(elementChildren(tok));
+ if (ns == null || !doc.createElementNS)
+ return doc.createElement(elementName(tok).substring(1));
+ return doc.createElementNS(ns, elementName(tok).substring(1));
+ }
+
+ if (elementHasValue(token)) {
+ var v = elementValue(token);
+ if (isList(v)) {
+ var e = expandElementValues(elementName(token), v);
+ writeList(e, node, doc);
+ } else {
+ var child = mkelem(token, doc);
+ writeList(elementChildren(token), child, doc);
+ node.appendChild(child);
+ }
+ } else {
+ var child = mkelem(token, doc);
+ writeList(elementChildren(token), child, doc);
+ node.appendChild(child);
+ }
+ } else
+ node.appendChild(doc.createTextNode('' + token));
+
+ writeList(cdr(l), node, doc);
+ return node;
+}
+
+/**
+ * Make a new XML document.
+ */
+function mkXMLDocument() {
+ if (document.implementation && document.implementation.createDocument)
+ return document.implementation.createDocument('', '', null);
+ return new ActiveXObject("MSXML2.DOMDocument");
+}
+
+/**
+ * Convert a list of values to a list of strings representing an XML document.
+ */
+function writeXML(l, xmlTag) {
+ var doc = mkXMLDocument();
+ writeList(l, doc, doc);
+ if (!xmlTag)
+ return writeXMLDocument(doc);
+ return mklist('<?xml version="1.0" encoding="UTF-8"?>\n' + writeXMLDocument(doc) + '\n');
+}
+