Browse Source

eclipse milo running as urcap poc

Martin Kunz 5 years ago
parent
commit
1c1bb1ad39
28 changed files with 2047 additions and 1288 deletions
  1. 6 0
      .idea/encodings.xml
  2. 34 0
      osgi.txt
  3. 27 62
      pom.xml
  4. 0 20
      src/main/java/at/acdp/opcur/Activator.java
  5. 0 254
      src/main/java/at/acdp/opcur/ExampleKeys.java
  6. 0 65
      src/main/java/at/acdp/opcur/HelloWorldInstallationNodeContribution.java
  7. 0 30
      src/main/java/at/acdp/opcur/HelloWorldInstallationNodeService.java
  8. 0 93
      src/main/java/at/acdp/opcur/HelloWorldProgramNodeContribution.java
  9. 0 45
      src/main/java/at/acdp/opcur/HelloWorldProgramNodeService.java
  10. 0 400
      src/main/java/at/acdp/opcur/OPCTest.java
  11. 0 319
      src/main/java/at/acdp/opcur/OPCTestPlain.java
  12. 110 0
      src/main/java/at/acdp/opcur/opc/CustomDataType.java
  13. 119 0
      src/main/java/at/acdp/opcur/opc/KeyStoreLoader.java
  14. 850 0
      src/main/java/at/acdp/opcur/opc/MyNamespace.java
  15. 61 0
      src/main/java/at/acdp/opcur/opc/RestrictedAccessDelegate.java
  16. 170 0
      src/main/java/at/acdp/opcur/opc/Server.java
  17. 48 0
      src/main/java/at/acdp/opcur/opc/SqrtMethod.java
  18. 62 0
      src/main/java/at/acdp/opcur/opc/ValueLoggingDelegate.java
  19. 26 0
      src/main/java/at/acdp/opcur/ur/Activator.java
  20. 78 0
      src/main/java/at/acdp/opcur/ur/HelloWorldInstallationNodeContribution.java
  21. 35 0
      src/main/java/at/acdp/opcur/ur/HelloWorldInstallationNodeService.java
  22. 89 0
      src/main/java/at/acdp/opcur/ur/HelloWorldInstallationNodeView.java
  23. 108 0
      src/main/java/at/acdp/opcur/ur/HelloWorldProgramNodeContribution.java
  24. 51 0
      src/main/java/at/acdp/opcur/ur/HelloWorldProgramNodeService.java
  25. 116 0
      src/main/java/at/acdp/opcur/ur/HelloWorldProgramNodeView.java
  26. 33 0
      src/main/java/at/acdp/opcur/ur/Style.java
  27. 12 0
      src/main/java/at/acdp/opcur/ur/V3Style.java
  28. 12 0
      src/main/java/at/acdp/opcur/ur/V5Style.java

+ 6 - 0
.idea/encodings.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$" charset="UTF-8" />
+  </component>
+</project>

+ 34 - 0
osgi.txt

@@ -0,0 +1,34 @@
+[ 100] [Resolved   ] [    1] commons-httpclient (3.1.0.0)
+[ 101] [Active     ] [    1] Profinet (1.1.8)
+[ 102] [Resolved   ] [    1] Commons BeanUtils (1.8.3)
+[ 103] [Resolved   ] [    1] Jackson-annotations (2.0.0)
+[ 104] [Active     ] [    1] polyscope-dashboard-server-impl (1.0.1065)
+[ 105] [Active     ] [    1] Help artifact (1.8.3)
+[ 106] [Resolved   ] [    1] org.swinglabs.layout (1.0.3)
+[ 107] [Active     ] [    1] view (7.18.240)
+[ 108] [Resolved   ] [    1] Apache Commons Lang (3.3.2)
+[ 109] [Active     ] [    1] viewfacade (7.18.240)
+[ 110] [Resolved   ] [    1] Guava: Google Core Libraries for Java (19.0.0)
+[ 115] [Resolved   ] [    1] Apache Commons Codec (1.11.0)
+[ 139] [Active     ] [    1] stack-server (0.2.4)
+[ 141] [Active     ] [    1] stack-core (0.2.4)
+[ 145] [Active     ] [    1] Netty/Buffer (4.1.30.Final)
+[ 146] [Active     ] [    1] Netty/Transport/Native/Unix/Common (4.1.30.Final)
+[ 147] [Active     ] [    1] Netty/Transport (4.1.30.Final)
+[ 148] [Active     ] [    1] Netty/Resolver/DNS (4.1.30.Final)
+[ 149] [Active     ] [    1] Netty/Common (4.1.30.Final)
+[ 150] [Active     ] [    1] sdk-core (0.2.4)
+[ 183] [Active     ] [    1] Metrics Core (4.0.3)
+[ 184] [Active     ] [    1] slf4j-api (1.7.25)
+[ 186] [Resolved   ] [    1] slf4j-jdk14 (1.7.25)
+[ 187] [Active     ] [    1] Netty/Handler (4.1.30.Final)
+[ 188] [Active     ] [    1] Netty/Codec (4.1.30.Final)
+[ 189] [Active     ] [    1] Netty/Resolver (4.1.30.Final)
+[ 190] [Resolved   ] [    1] Netty/Codec/DNS (4.1.30.Final)
+[ 191] [Active     ] [    1] Metrics Core (3.2.6)
+[ 192] [Active     ] [    1] sdk-server (0.2.4)
+[ 195] [Active     ] [    1] jOOL (0.9.10)
+[ 198] [Resolved   ] [    1] Hello World (1.0.0.SNAPSHOT)
+[ 199] [Active     ] [    1] bcprov (1.60)
+[ 200] [Installed  ] [    1] bcmail (1.60)
+[ 201] [Active     ] [    1] bcpkix (1.60)

+ 27 - 62
pom.xml

@@ -2,17 +2,14 @@
 <project
 		xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
 	<modelVersion>4.0.0</modelVersion>
-
 	<groupId>at.acdp.opcur</groupId>
 	<artifactId>helloworld</artifactId>
 	<version>1.0-SNAPSHOT</version>
 	<name>Hello World</name>
 	<packaging>bundle</packaging>
-
 	<properties>
-
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<!--********************************************************************-->
 		<!--   Note: Update this section with relevant meta data                -->
 		<!--         that comes along with your URCap                           -->
@@ -43,57 +40,22 @@
 
 	<build>
 		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-shade-plugin</artifactId>
-				<version>3.2.0</version>
-				<executions>
-					<execution>
-						<phase>package</phase>
-						<goals>
-							<goal>shade</goal>
-						</goals>
-						<configuration>
-							<filters>
-								<filter>
-									<artifact>junit:junit</artifact>
-									<includes>
-										<include>junit/framework/**</include>
-										<include>org/junit/**</include>
-									</includes>
-									<excludes>
-										<exclude>org/junit/experimental/**</exclude>
-										<exclude>org/junit/runners/**</exclude>
-									</excludes>
-								</filter>
-								<filter>
-									<artifact>*:*</artifact>
-									<excludes>
-										<exclude>META-INF/*.SF</exclude>
-										<exclude>META-INF/*.DSA</exclude>
-										<exclude>META-INF/*.RSA</exclude>
-									</excludes>
-								</filter>
-							</filters>
-						</configuration>
-					</execution>
-				</executions>
-			</plugin>
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
-				<version>3.6.0</version>
+				<version>3.8.0</version>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+					<source>1.8</source>
+					<target>1.8</target>
 				</configuration>
 			</plugin>
 			<plugin>
 				<artifactId>maven-resources-plugin</artifactId>
-				<version>3.0.2</version>
+				<version>3.1.0</version>
 			</plugin>
 			<plugin>
 				<artifactId>maven-jar-plugin</artifactId>
+				<version>3.1.0</version>
 				<configuration>
 					<archive>
 						<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
@@ -103,7 +65,7 @@
 			<plugin>
 				<groupId>org.apache.felix</groupId>
 				<artifactId>maven-bundle-plugin</artifactId>
-				<version>2.4.0</version>
+				<version>4.0.0</version>
 				<extensions>true</extensions>
 				<executions>
 					<execution>
@@ -118,7 +80,7 @@
 					<instructions>
 						<!--********** DO NOT MODIFY THE ENTRIES OF THIS SECTION **********-->
 						<Bundle-Category>URCap</Bundle-Category>
-						<Bundle-Activator>at.acdp.opcur.Activator</Bundle-Activator>
+						<Bundle-Activator>at.acdp.opcur.ur.Activator</Bundle-Activator>
 						<Bundle-Vendor>${urcap.vendor}</Bundle-Vendor>
 						<Bundle-ContactAddress>${urcap.contactAddress}</Bundle-ContactAddress>
 						<Bundle-Copyright>${urcap.copyright}</Bundle-Copyright>
@@ -129,15 +91,13 @@
 							com.ur.urcap.api*;version="[1.0.0,2.0.0)",
 							*
 						</Import-Package>
-
-						<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
 					</instructions>
 				</configuration>
 			</plugin>
 			<plugin>
 				<groupId>org.codehaus.mojo</groupId>
 				<artifactId>exec-maven-plugin</artifactId>
-				<version>1.1</version>
+				<version>1.6.0</version>
 				<executions>
 					<!-- generate URCap package after compiling -->
 					<execution>
@@ -173,6 +133,23 @@
 		</plugins>
 	</build>
 	<dependencies>
+		<!-- https://mvnrepository.com/artifact/org.eclipse.milo/sdk-server -->
+		<dependency>
+			<groupId>org.eclipse.milo</groupId>
+			<artifactId>sdk-server</artifactId>
+			<version>0.2.4</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.bouncycastle</groupId>
+			<artifactId>bcprov-jdk15on</artifactId>
+			<version>1.60</version>
+		</dependency>
+		<dependency>
+			<groupId>org.bouncycastle</groupId>
+			<artifactId>bcmail-jdk15on</artifactId>
+			<version>1.60</version>
+		</dependency>
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
@@ -183,24 +160,17 @@
 			<artifactId>slf4j-simple</artifactId>
 			<version>1.7.25</version>
 		</dependency>
-		<dependency>
-			<groupId>org.opcfoundation.ua</groupId>
-			<artifactId>opc-ua-stack</artifactId>
-			<version>1.3.345a-SNAPSHOT</version>
-		</dependency>
 		<dependency>
 			<groupId>org.osgi</groupId>
 			<artifactId>org.osgi.core</artifactId>
-			<version>6.0.0</version>
+			<version>4.3.0</version>
 		</dependency>
-
 		<dependency>
 			<groupId>com.ur.urcap</groupId>
 			<artifactId>api</artifactId>
 			<version>1.4.0</version>
 			<scope>provided</scope>
 		</dependency>
-
 		<!-- test dependencies -->
 		<dependency>
 			<groupId>junit</groupId>
@@ -208,11 +178,6 @@
 			<version>4.12</version>
 			<scope>test</scope>
 		</dependency>
-		<dependency>
-			<groupId>org.apache.httpcomponents</groupId>
-			<artifactId>httpclient</artifactId>
-			<version>4.5.6</version>
-		</dependency>
 	</dependencies>
 
 	<profiles>

+ 0 - 20
src/main/java/at/acdp/opcur/Activator.java

@@ -1,20 +0,0 @@
-package at.acdp.opcur;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import com.ur.urcap.api.contribution.InstallationNodeService;
-import com.ur.urcap.api.contribution.ProgramNodeService;
-
-public class Activator implements BundleActivator {
-	@Override
-	public void start(final BundleContext context) throws Exception {
-		HelloWorldInstallationNodeService helloWorldInstallationNodeService = new HelloWorldInstallationNodeService();
-
-		context.registerService(InstallationNodeService.class, helloWorldInstallationNodeService, null);
-		context.registerService(ProgramNodeService.class, new HelloWorldProgramNodeService(), null);
-	}
-
-	@Override
-	public void stop(BundleContext context) throws Exception {
-	}
-}

+ 0 - 254
src/main/java/at/acdp/opcur/ExampleKeys.java

@@ -1,254 +0,0 @@
-package at.acdp.opcur;
-/* ========================================================================
- * Copyright (c) 2005-2015 The OPC Foundation, Inc. All rights reserved.
- *
- * OPC Foundation MIT License 1.00
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * The complete license agreement can be found here:
- * http://opcfoundation.org/License/MIT/1.00/
- * ======================================================================*/
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.InvalidParameterSpecException;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-
-import org.opcfoundation.ua.common.ServiceResultException;
-import org.opcfoundation.ua.transport.security.Cert;
-import org.opcfoundation.ua.transport.security.KeyPair;
-import org.opcfoundation.ua.transport.security.PrivKey;
-import org.opcfoundation.ua.utils.CertificateUtils;
-import org.opcfoundation.ua.utils.CryptoUtil;
-
-/**
- * Keys for examples
- * Keystore.p12 contains 20 RSA keypairs with the following aliases
- *
- * alias               dname
- *
- * server_8192         CN=server
- * server_4096         CN=server
- * server_2048         CN=server
- * server_1024         CN=server
- * server_512          CN=server
- *
- * client_8192         CN=client
- * client_4096         CN=client
- * client_2048         CN=client
- * client_1024         CN=client
- * client_512          CN=client
- *
- * https_server_8192   CN=https_server
- * https_server_4096   CN=https_server
- * https_server_2048   CN=https_server
- * https_server_1024   CN=https_server
- * https_server_512    CN=https_server
- *
- * https_client_8192   CN=https_client
- * https_client_4096   CN=https_client
- * https_client_2048   CN=https_client
- * https_client_1024   CN=https_client
- * https_client_512    CN=https_client
- *
- * Keystore password is "password".
- * Private key passwords are "password".
- *
- */
-public class ExampleKeys {
-    /**
-     * Load file certificate and private key from applicationName.der & .pfx - or create ones if they do not exist
-     * @return the KeyPair composed of the certificate and private key
-     * @throws ServiceResultException
-     */
-    public static KeyPair getCert(String applicationName)
-            throws ServiceResultException
-    {
-        File certFile = new File(applicationName + ".der");
-        File privKeyFile =  new File(applicationName+ ".pem");
-        try {
-            Cert myCertificate = Cert.load( certFile );
-            PrivKey myPrivateKey = PrivKey.load( privKeyFile);
-            return new KeyPair(myCertificate, myPrivateKey);
-        } catch (CertificateException e) {
-            throw new ServiceResultException( e );
-        } catch (IOException e) {
-            try {
-                String hostName = InetAddress.getLocalHost().getHostName();
-                String applicationUri = "urn:"+hostName+":"+applicationName;
-                KeyPair keys = CertificateUtils.createApplicationInstanceCertificate(applicationName, null, applicationUri, 3650, hostName);
-                keys.getCertificate().save(certFile);
-                keys.getPrivateKey().save(privKeyFile);
-                return keys;
-            } catch (Exception e1) {
-                throw new ServiceResultException( e1 );
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new ServiceResultException( e );
-        } catch (InvalidKeySpecException e) {
-            throw new ServiceResultException( e );
-        }
-    }
-
-    /**
-     * Load CA certificate and private key from SampleCA.der & .pfx - or create ones if they do not exist
-     * @return the KeyPair composed of the certificate and private key
-     * @throws ServiceResultException
-     */
-    public static KeyPair getCACert()
-            throws ServiceResultException
-    {
-        File certFile = new File("SampleCA.der");
-        File privKeyFile =  new File("SampleCA.pem");
-        try {
-            Cert myCertificate = Cert.load( certFile );
-            PrivKey myPrivateKey = PrivKey.load( privKeyFile);
-            return new KeyPair(myCertificate, myPrivateKey);
-        } catch (CertificateException e) {
-            throw new ServiceResultException( e );
-        } catch (IOException e) {
-            try {
-                KeyPair keys = CertificateUtils.createIssuerCertificate("SampleCA", 3650, null);
-                keys.getCertificate().save(certFile);
-                keys.getPrivateKey().save(privKeyFile);
-                return keys;
-            } catch (Exception e1) {
-                throw new ServiceResultException( e1 );
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new ServiceResultException( e );
-        } catch (InvalidKeySpecException e) {
-            throw new ServiceResultException( e );
-        }
-    }
-    /**
-     * Load file certificate and private key from applicationName.der & .pfx - or create ones if they do not exist
-     * @param applicationName
-     * @return the KeyPair composed of the certificate and private key
-     * @throws ServiceResultException
-     */
-    public static KeyPair getHttpsCert(String applicationName)
-            throws ServiceResultException
-    {
-        File certFile = new File(applicationName + "_https.der");
-        File privKeyFile =  new File(applicationName+ "_https.pem");
-        try {
-            Cert myCertificate = Cert.load( certFile );
-            PrivKey myPrivateKey = PrivKey.load( privKeyFile);
-            return new KeyPair(myCertificate, myPrivateKey);
-        } catch (CertificateException e) {
-            throw new ServiceResultException( e );
-        } catch (IOException e) {
-            try {
-                KeyPair caCert = getCACert();
-                String hostName = InetAddress.getLocalHost().getHostName();
-                String applicationUri = "urn:"+hostName+":"+applicationName;
-                KeyPair keys =   CertificateUtils.createHttpsCertificate(hostName, applicationUri, 3650, caCert);
-                keys.getCertificate().save(certFile);
-                keys.getPrivateKey().save(privKeyFile);
-                return keys;
-            } catch (Exception e1) {
-                throw new ServiceResultException( e1 );
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new ServiceResultException( e );
-        } catch (InvalidKeySpecException e) {
-            throw new ServiceResultException( e );
-        }
-    }
-    /**
-     * Open keypair from keystore.p12 used in some of these examples.
-     *
-     * Usable aliases are : "server", "client", "https_server", "https_client"
-     * Usable keysizes are : 8192, 4096, 2048, 1024
-     *
-     * @param alias
-     * @param keysize
-     * @return
-     * @throws KeyStoreException
-     * @throws IOException
-     * @throws CertificateException
-     * @throws NoSuchAlgorithmException
-     * @throws UnrecoverableKeyException
-     */
-//    public static KeyPair getKeyPair(String alias, int keysize) throws ServiceResultException {
-//        try {
-//            Certificate cert = ks.getCertificate(alias+"_"+keysize);
-//            Key key = ks.getKey(alias+"_"+keysize, "password".toCharArray());
-//            KeyPair pair = new KeyPair( new Cert( (X509Certificate) cert ), new PrivKey( (RSAPrivateKey) key ) );
-//            return pair;
-//        } catch (KeyStoreException e) {
-//            throw new ServiceResultException( e );
-//        } catch (UnrecoverableKeyException e) {
-//            throw new ServiceResultException( e );
-//        } catch (NoSuchAlgorithmException e) {
-//            throw new ServiceResultException( e );
-//        } catch (CertificateEncodingException e) {
-//            throw new ServiceResultException( e );
-//        }
-//    }
-
-    //static KeyStore ks;
-
-//    static {
-//        try {
-//            ks = KeyStore.getInstance("pkcs12");
-//            InputStream is = ExampleKeys.class.getResourceAsStream("keystore.p12");
-//            try {
-//                ks.load( is, "password".toCharArray() );
-//            } catch (NoSuchAlgorithmException e) {
-//                throw new RuntimeException(e);
-//            } catch (CertificateException e) {
-//                throw new RuntimeException(e);
-//            } catch (IOException e) {
-//                throw new RuntimeException(e);
-//            } finally {
-//                try {
-//                    is.close();
-//                } catch (IOException e) {
-//                }
-//            }
-//        } catch (KeyStoreException e) {
-//            throw new RuntimeException(e);
-//        }
-//    }
-
-
-}

+ 0 - 65
src/main/java/at/acdp/opcur/HelloWorldInstallationNodeContribution.java

@@ -1,65 +0,0 @@
-package at.acdp.opcur;
-
-import com.ur.urcap.api.contribution.InstallationNodeContribution;
-import com.ur.urcap.api.domain.data.DataModel;
-import com.ur.urcap.api.domain.script.ScriptWriter;
-import com.ur.urcap.api.ui.annotation.Input;
-import com.ur.urcap.api.ui.component.InputEvent;
-import com.ur.urcap.api.ui.component.InputTextField;
-
-public class HelloWorldInstallationNodeContribution implements InstallationNodeContribution {
-
-	private static final String POPUPTITLE_KEY = "popuptitle";
-	private static final String DEFAULT_VALUE = "Hello World";
-
-	private DataModel model;
-
-	public HelloWorldInstallationNodeContribution(DataModel model) {
-		this.model = model;
-	}
-
-	@Input(id = POPUPTITLE_KEY)
-	private InputTextField popupTitleField;
-
-	@Input(id = POPUPTITLE_KEY)
-	public void onMessageChange(InputEvent event) {
-		if (event.getEventType() == InputEvent.EventType.ON_CHANGE) {
-			setPopupTitle(popupTitleField.getText());
-		}
-	}
-
-	@Override
-	public void openView() {
-		popupTitleField.setText(getPopupTitle());
-	}
-
-	@Override
-	public void closeView() { }
-
-	public boolean isDefined() {
-		return !getPopupTitle().isEmpty();
-	}
-
-	@Override
-	public void generateScript(ScriptWriter writer) {
-		// Store the popup title in a global variable so it is globally available to all HelloWorld program nodes.
-		writer.assign("hello_world_popup_title", "\"" + getPopupTitle() + "\"");
-	}
-
-	public String getPopupTitle() {
-		return model.get(POPUPTITLE_KEY, DEFAULT_VALUE);
-	}
-
-	private void setPopupTitle(String message) {
-		if ("".equals(message)) {
-			resetToDefaultValue();
-		} else {
-			model.set(POPUPTITLE_KEY, message);
-		}
-	}
-
-	private void resetToDefaultValue() {
-		popupTitleField.setText(DEFAULT_VALUE);
-		model.set(POPUPTITLE_KEY, DEFAULT_VALUE);
-	}
-}

+ 0 - 30
src/main/java/at/acdp/opcur/HelloWorldInstallationNodeService.java

@@ -1,30 +0,0 @@
-package at.acdp.opcur;
-
-import com.ur.urcap.api.contribution.InstallationNodeContribution;
-import com.ur.urcap.api.contribution.InstallationNodeService;
-import com.ur.urcap.api.domain.URCapAPI;
-
-import java.io.InputStream;
-
-import com.ur.urcap.api.domain.data.DataModel;
-
-public class HelloWorldInstallationNodeService implements InstallationNodeService {
-
-	public HelloWorldInstallationNodeService() { }
-
-	@Override
-	public InstallationNodeContribution createInstallationNode(URCapAPI api, DataModel model) {
-		return new HelloWorldInstallationNodeContribution(model);
-	}
-
-	@Override
-	public String getTitle() {
-		return "Hello World";
-	}
-
-	@Override
-	public InputStream getHTML() {
-		InputStream is = this.getClass().getResourceAsStream("/at.acdp.opcur/installation.html");
-		return is;
-	}
-}

+ 0 - 93
src/main/java/at/acdp/opcur/HelloWorldProgramNodeContribution.java

@@ -1,93 +0,0 @@
-package at.acdp.opcur;
-
-import com.ur.urcap.api.contribution.ProgramNodeContribution;
-import com.ur.urcap.api.domain.URCapAPI;
-import com.ur.urcap.api.domain.data.DataModel;
-import com.ur.urcap.api.domain.script.ScriptWriter;
-import com.ur.urcap.api.ui.annotation.Input;
-import com.ur.urcap.api.ui.annotation.Label;
-import com.ur.urcap.api.ui.component.InputEvent;
-import com.ur.urcap.api.ui.component.InputTextField;
-import com.ur.urcap.api.ui.component.LabelComponent;
-
-public class HelloWorldProgramNodeContribution implements ProgramNodeContribution {
-	private static final String NAME = "name";
-
-	private final DataModel model;
-	private final URCapAPI api;
-
-	public HelloWorldProgramNodeContribution(URCapAPI api, DataModel model) {
-		this.api = api;
-		this.model = model;
-	}
-
-	@Input(id = "yourname")
-	private InputTextField nameTextField;
-
-	@Label(id = "titlePreviewLabel")
-	private LabelComponent titlePreviewLabel;
-
-	@Label(id = "messagePreviewLabel")
-	private LabelComponent messagePreviewLabel;
-
-	@Input(id = "yourname")
-	public void onInput(InputEvent event) {
-		if (event.getEventType() == InputEvent.EventType.ON_CHANGE) {
-			setName(nameTextField.getText());
-			updatePopupMessageAndPreview();
-		}
-	}
-
-	@Override
-	public void openView() {
-		nameTextField.setText(getName());
-		updatePopupMessageAndPreview();
-	}
-
-	@Override
-	public void closeView() {
-	}
-
-	@Override
-	public String getTitle() {
-		return "Hello World: " + (model.isSet(NAME) ? getName() : "");
-	}
-
-	@Override
-	public boolean isDefined() {
-		return getInstallation().isDefined() && !getName().isEmpty();
-	}
-
-	@Override
-	public void generateScript(ScriptWriter writer) {
-		// Directly generate this Program Node's popup message + access the popup title through a global variable
-		writer.appendLine("popup(\"" + generatePopupMessage() + "\", hello_world_popup_title, False, False, blocking=True)");
-		writer.writeChildren();
-	}
-
-	private String generatePopupMessage() {
-		return model.isSet(NAME) ? "Hello " + getName() + ", welcome to PolyScope!" : "No name set";
-	}
-
-	private void updatePopupMessageAndPreview() {
-		messagePreviewLabel.setText(generatePopupMessage());
-		titlePreviewLabel.setText(getInstallation().isDefined() ? getInstallation().getPopupTitle() : "No title set");
-	}
-
-	private String getName() {
-		return model.get(NAME, "");
-	}
-
-	private void setName(String name) {
-		if ("".equals(name)){
-			model.remove(NAME);
-		}else{
-			model.set(NAME, name);
-		}
-	}
-
-	private HelloWorldInstallationNodeContribution getInstallation() {
-		return api.getInstallationNode(HelloWorldInstallationNodeContribution.class);
-	}
-
-}

+ 0 - 45
src/main/java/at/acdp/opcur/HelloWorldProgramNodeService.java

@@ -1,45 +0,0 @@
-package at.acdp.opcur;
-
-import com.ur.urcap.api.contribution.ProgramNodeContribution;
-import com.ur.urcap.api.contribution.ProgramNodeService;
-import com.ur.urcap.api.domain.URCapAPI;
-import com.ur.urcap.api.domain.data.DataModel;
-
-import java.io.InputStream;
-
-public class HelloWorldProgramNodeService implements ProgramNodeService {
-
-	public HelloWorldProgramNodeService() {
-	}
-
-	@Override
-	public String getId() {
-		return "HelloWorldNode";
-	}
-
-	@Override
-	public String getTitle() {
-		return "Hello World";
-	}
-
-	@Override
-	public InputStream getHTML() {
-		InputStream is = this.getClass().getResourceAsStream("/at.acdp.opcur/programnode.html");
-		return is;
-	}
-
-	@Override
-	public boolean isDeprecated() {
-		return false;
-	}
-
-	@Override
-	public boolean isChildrenAllowed() {
-		return true;
-	}
-
-	@Override
-	public ProgramNodeContribution createNode(URCapAPI api, DataModel model) {
-		return new HelloWorldProgramNodeContribution(api, model);
-	}
-}

+ 0 - 400
src/main/java/at/acdp/opcur/OPCTest.java

@@ -1,400 +0,0 @@
-package at.acdp.opcur;
-/*
- * ======================================================================== Copyright (c) 2005-2015
- * The OPC Foundation, Inc. All rights reserved.
- *
- * OPC Foundation MIT License 1.00
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
- * associated documentation files (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or
- * substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
- * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- *
- * The complete license agreement can be found here: http://opcfoundation.org/License/MIT/1.00/
- * ======================================================================
- */
-
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.interfaces.RSAPrivateKey;
-import java.util.Random;
-import java.util.UUID;
-
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.opcfoundation.ua.application.Application;
-import org.opcfoundation.ua.application.Server;
-import org.opcfoundation.ua.builtintypes.ByteString;
-import org.opcfoundation.ua.builtintypes.DataValue;
-import org.opcfoundation.ua.builtintypes.LocalizedText;
-import org.opcfoundation.ua.builtintypes.NodeId;
-import org.opcfoundation.ua.builtintypes.QualifiedName;
-import org.opcfoundation.ua.builtintypes.StatusCode;
-import org.opcfoundation.ua.builtintypes.UnsignedInteger;
-import org.opcfoundation.ua.builtintypes.Variant;
-import org.opcfoundation.ua.common.ServiceFaultException;
-import org.opcfoundation.ua.common.ServiceResultException;
-import org.opcfoundation.ua.core.ActivateSessionRequest;
-import org.opcfoundation.ua.core.ActivateSessionResponse;
-import org.opcfoundation.ua.core.AddNodesRequest;
-import org.opcfoundation.ua.core.AddNodesResponse;
-import org.opcfoundation.ua.core.AddReferencesRequest;
-import org.opcfoundation.ua.core.AddReferencesResponse;
-import org.opcfoundation.ua.core.AttributeServiceSetHandler;
-import org.opcfoundation.ua.core.Attributes;
-import org.opcfoundation.ua.core.BrowseNextRequest;
-import org.opcfoundation.ua.core.BrowseNextResponse;
-import org.opcfoundation.ua.core.BrowseRequest;
-import org.opcfoundation.ua.core.BrowseResponse;
-import org.opcfoundation.ua.core.BrowseResult;
-import org.opcfoundation.ua.core.CancelRequest;
-import org.opcfoundation.ua.core.CancelResponse;
-import org.opcfoundation.ua.core.CloseSessionRequest;
-import org.opcfoundation.ua.core.CloseSessionResponse;
-import org.opcfoundation.ua.core.CreateSessionRequest;
-import org.opcfoundation.ua.core.CreateSessionResponse;
-import org.opcfoundation.ua.core.DeleteNodesRequest;
-import org.opcfoundation.ua.core.DeleteNodesResponse;
-import org.opcfoundation.ua.core.DeleteReferencesRequest;
-import org.opcfoundation.ua.core.DeleteReferencesResponse;
-import org.opcfoundation.ua.core.EndpointConfiguration;
-import org.opcfoundation.ua.core.HistoryReadRequest;
-import org.opcfoundation.ua.core.HistoryReadResponse;
-import org.opcfoundation.ua.core.HistoryUpdateRequest;
-import org.opcfoundation.ua.core.HistoryUpdateResponse;
-import org.opcfoundation.ua.core.Identifiers;
-import org.opcfoundation.ua.core.NodeManagementServiceSetHandler;
-import org.opcfoundation.ua.core.QueryFirstRequest;
-import org.opcfoundation.ua.core.QueryFirstResponse;
-import org.opcfoundation.ua.core.QueryNextRequest;
-import org.opcfoundation.ua.core.QueryNextResponse;
-import org.opcfoundation.ua.core.ReadRequest;
-import org.opcfoundation.ua.core.ReadResponse;
-import org.opcfoundation.ua.core.ReadValueId;
-import org.opcfoundation.ua.core.RegisterNodesRequest;
-import org.opcfoundation.ua.core.RegisterNodesResponse;
-import org.opcfoundation.ua.core.ServiceFault;
-import org.opcfoundation.ua.core.SessionServiceSetHandler;
-import org.opcfoundation.ua.core.SignatureData;
-import org.opcfoundation.ua.core.StatusCodes;
-import org.opcfoundation.ua.core.TranslateBrowsePathsToNodeIdsRequest;
-import org.opcfoundation.ua.core.TranslateBrowsePathsToNodeIdsResponse;
-import org.opcfoundation.ua.core.UnregisterNodesRequest;
-import org.opcfoundation.ua.core.UnregisterNodesResponse;
-import org.opcfoundation.ua.core.UserTokenPolicy;
-import org.opcfoundation.ua.core.WriteRequest;
-import org.opcfoundation.ua.core.WriteResponse;
-import org.opcfoundation.ua.transport.endpoint.EndpointServiceRequest;
-import org.opcfoundation.ua.transport.security.CertificateValidator;
-import org.opcfoundation.ua.transport.security.HttpsSecurityPolicy;
-import org.opcfoundation.ua.transport.security.KeyPair;
-import org.opcfoundation.ua.transport.security.SecurityAlgorithm;
-import org.opcfoundation.ua.transport.security.SecurityMode;
-import org.opcfoundation.ua.transport.security.SecurityPolicy;
-import org.opcfoundation.ua.utils.CryptoUtil;
-import org.opcfoundation.ua.utils.EndpointUtil;
-
-
-/**
- * Simple Server example. This server responds to stack test and endpoint discover service requests.
- *
- */
-public class OPCTest {
-
-
-    static class MyAttributeServiceHandler implements AttributeServiceSetHandler {
-
-        @Override
-        public void onHistoryRead(EndpointServiceRequest<HistoryReadRequest, HistoryReadResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onHistoryUpdate(EndpointServiceRequest<HistoryUpdateRequest, HistoryUpdateResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onRead(EndpointServiceRequest<ReadRequest, ReadResponse> req) throws ServiceFaultException {
-            ReadRequest request = req.getRequest();
-            ReadValueId[] nodesToRead = request.getNodesToRead();
-
-            DataValue[] results = new DataValue[nodesToRead.length];
-            for (int i = 0; i < nodesToRead.length; i++) {
-                if (Identifiers.RootFolder.equals(nodesToRead[i].getNodeId())) {
-                    if (Attributes.BrowseName.equals(nodesToRead[i].getAttributeId())) {
-                        results[i] = new DataValue(new Variant(new QualifiedName("Root")));
-                    } else if (Attributes.DisplayName.equals(nodesToRead[i].getAttributeId())) {
-                        results[i] = new DataValue(new Variant(new LocalizedText("Root", LocalizedText.NO_LOCALE)));
-                    } else {
-                        results[i] = new DataValue(new StatusCode(StatusCodes.Bad_AttributeIdInvalid));
-                    }
-                } else {
-                    results[i] = new DataValue(new StatusCode(StatusCodes.Bad_NodeIdUnknown));
-                }
-            }
-            ReadResponse response = new ReadResponse(null, results, null);
-            req.sendResponse(response);
-        }
-
-        @Override
-        public void onWrite(EndpointServiceRequest<WriteRequest, WriteResponse> req) throws ServiceFaultException {
-
-        }
-
-    };
-
-    static class MyNodeManagementServiceHandler implements NodeManagementServiceSetHandler {
-
-        @Override
-        public void onAddNodes(EndpointServiceRequest<AddNodesRequest, AddNodesResponse> req) throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onAddReferences(EndpointServiceRequest<AddReferencesRequest, AddReferencesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onBrowse(EndpointServiceRequest<BrowseRequest, BrowseResponse> req) throws ServiceFaultException {
-            BrowseRequest request = req.getRequest();
-            BrowseResult[] Results = new BrowseResult[request.getNodesToBrowse().length];
-            for (int i = 0; i < request.getNodesToBrowse().length; i++) {
-                StatusCode statusCode;
-                if (Identifiers.RootFolder.equals(request.getNodesToBrowse()[i].getNodeId())) {
-                    statusCode = StatusCode.GOOD;
-                } else {
-                    statusCode = new StatusCode(StatusCodes.Bad_NodeIdUnknown);
-                }
-                Results[i] = new BrowseResult(statusCode, null, null);
-            }
-            BrowseResponse response = new BrowseResponse(null, Results, null);
-            req.sendResponse(response);
-
-        }
-
-        @Override
-        public void onBrowseNext(EndpointServiceRequest<BrowseNextRequest, BrowseNextResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onDeleteNodes(EndpointServiceRequest<DeleteNodesRequest, DeleteNodesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onDeleteReferences(EndpointServiceRequest<DeleteReferencesRequest, DeleteReferencesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onQueryFirst(EndpointServiceRequest<QueryFirstRequest, QueryFirstResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onQueryNext(EndpointServiceRequest<QueryNextRequest, QueryNextResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onRegisterNodes(EndpointServiceRequest<RegisterNodesRequest, RegisterNodesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onTranslateBrowsePathsToNodeIds(
-                EndpointServiceRequest<TranslateBrowsePathsToNodeIdsRequest, TranslateBrowsePathsToNodeIdsResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onUnregisterNodes(EndpointServiceRequest<UnregisterNodesRequest, UnregisterNodesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-    }
-
-
-    static class MyServerExample extends Server implements SessionServiceSetHandler {
-
-        public MyServerExample(Application application) throws Exception {
-            super(application);
-            addServiceHandler(this);
-
-            // Add Client Application Instance Certificate validator - Accept them all (for now)
-            application.getOpctcpSettings().setCertificateValidator(CertificateValidator.ALLOW_ALL);
-            application.getHttpsSettings().setCertificateValidator(CertificateValidator.ALLOW_ALL);
-
-            // The HTTPS SecurityPolicies are defined separate from the endpoint securities
-            application.getHttpsSettings().setHttpsSecurityPolicies(HttpsSecurityPolicy.ALL);
-
-            // Peer verifier
-            application.getHttpsSettings().setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
-
-            // Load Servers's Application Instance Certificate...
-            KeyPair myServerApplicationInstanceCertificate = ExampleKeys.getCert("ServerExample1");
-            application.addApplicationInstanceCertificate(myServerApplicationInstanceCertificate);
-            // ...and HTTPS certificate
-
-
-            KeyPair myHttpsCertificate = ExampleKeys.getHttpsCert("ServerExample1");
-            application.getHttpsSettings().setKeyPair(myHttpsCertificate);
-
-            // Add User Token Policies
-            addUserTokenPolicy(UserTokenPolicy.ANONYMOUS);
-            addUserTokenPolicy(UserTokenPolicy.SECURE_USERNAME_PASSWORD);
-
-            // Create an endpoint for each network interface
-            String hostname = EndpointUtil.getHostname();
-            String bindAddress, endpointAddress;
-            for (String addr : EndpointUtil.getInetAddressNames()) {
-                bindAddress = "https://" + addr + ":8443/UAExample";
-                endpointAddress = "https://" + hostname + ":8443/UAExample";
-                System.out.println(endpointAddress + " bound at " + bindAddress);
-                // The HTTPS ports are using NONE OPC security
-                bind(bindAddress, endpointAddress, SecurityMode.NONE);
-
-                bindAddress = "opc.tcp://" + addr + ":8666/UAExample";
-                endpointAddress = "opc.tcp://" + hostname + ":8666/UAExample";
-                System.out.println(endpointAddress + " bound at " + bindAddress);
-                bind(bindAddress, endpointAddress, SecurityMode.ALL);
-            }
-
-            //////////////////////////////////////
-        }
-
-        @Override
-        public void onActivateSession(EndpointServiceRequest<ActivateSessionRequest, ActivateSessionResponse> msgExchange)
-                throws ServiceFaultException {
-            ActivateSessionResponse res = new ActivateSessionResponse();
-            res.setServerNonce(CryptoUtil.createNonce(32));
-            res.setResults(new StatusCode[] {StatusCode.GOOD});
-            msgExchange.sendResponse(res);
-        }
-
-        @Override
-        public void onCancel(EndpointServiceRequest<CancelRequest, CancelResponse> msgExchange)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onCloseSession(EndpointServiceRequest<CloseSessionRequest, CloseSessionResponse> msgExchange)
-                throws ServiceFaultException {
-            CloseSessionResponse res = new CloseSessionResponse();
-            msgExchange.sendResponse(res);
-        }
-
-        @Override
-        public void onCreateSession(EndpointServiceRequest<CreateSessionRequest, CreateSessionResponse> msgExchange)
-                throws ServiceFaultException {
-            CreateSessionRequest req = msgExchange.getRequest();
-            CreateSessionResponse res = new CreateSessionResponse();
-            byte[] token = new byte[32];
-            byte[] nonce = new byte[32];
-            Random r = new Random();
-            r.nextBytes(nonce);
-            r.nextBytes(token);
-            res.setAuthenticationToken(new NodeId(0, token));
-            EndpointConfiguration endpointConfiguration = EndpointConfiguration.defaults();
-            res.setMaxRequestMessageSize(UnsignedInteger
-                    .valueOf(Math.max(endpointConfiguration.getMaxMessageSize(), req.getMaxResponseMessageSize().longValue())));
-            res.setRevisedSessionTimeout(Math.max(req.getRequestedSessionTimeout(), 60 * 1000));
-            KeyPair cert = getApplication().getApplicationInstanceCertificates()[0];
-            res.setServerCertificate(ByteString.valueOf(cert.getCertificate().getEncoded()));
-            res.setServerEndpoints(this.getEndpointDescriptions());
-            res.setServerNonce(ByteString.valueOf(nonce));
-            ByteString clientCertificate = req.getClientCertificate();
-            ByteString clientNonce = req.getClientNonce();
-            SecurityPolicy securityPolicy = msgExchange.getChannel().getSecurityPolicy();
-            res.setServerSignature(
-                    getServerSignature(clientCertificate, clientNonce, securityPolicy, cert.getPrivateKey().getPrivateKey()));
-
-            res.setServerSoftwareCertificates(getApplication().getSoftwareCertificates());
-            res.setSessionId(new NodeId(0, "Session-" + UUID.randomUUID()));
-            msgExchange.sendResponse(res);
-        }
-
-        private SignatureData getServerSignature(ByteString clientCertificate, ByteString clientNonce,
-                                                 SecurityPolicy securityPolicy, final RSAPrivateKey privateKey) throws ServiceFaultException {
-            if (clientCertificate != null) {
-                ByteArrayOutputStream s = new ByteArrayOutputStream();
-                try {
-                    s.write(clientCertificate.getValue());
-                } catch (IOException e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_SecurityChecksFailed));
-                } catch (Exception e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NonceInvalid));
-                }
-                try {
-                    s.write(clientNonce.getValue());
-                } catch (IOException e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NonceInvalid));
-                } catch (Exception e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NonceInvalid));
-                }
-                try {
-                    SecurityAlgorithm algorithm = securityPolicy.getAsymmetricSignatureAlgorithm();
-                    return new SignatureData(algorithm.getUri(),
-                            ByteString.valueOf(CryptoUtil.getCryptoProvider().signAsymm(privateKey, algorithm, s.toByteArray())));
-
-                } catch (ServiceResultException e) {
-                    throw new ServiceFaultException(e);
-                }
-            }
-            return null;
-        }
-    }
-
-    public static void main(String[] args) throws Exception {
-        CryptoUtil.setSecurityProviderName("SunJCE");
-        ////////////// SERVER //////////////
-        // Create UA Server Application
-        // Create UA Service Server
-        Application myServerApplication = new Application();
-        MyServerExample myServer = new MyServerExample(myServerApplication);
-
-        myServer.addServiceHandler(new MyNodeManagementServiceHandler());
-        myServer.addServiceHandler(new MyAttributeServiceHandler());
-
-        //////////////////////////////////////
-        // Press enter to shutdown
-        System.out.println("Press enter to shutdown");
-        System.in.read();
-        //////////////////////////////////////
-
-
-        ///////////// SHUTDOWN /////////////
-        // Close the server by unbinding all endpoints
-        myServer.getApplication().close();
-        //////////////////////////////////////
-
-    }
-
-}

+ 0 - 319
src/main/java/at/acdp/opcur/OPCTestPlain.java

@@ -1,319 +0,0 @@
-package at.acdp.opcur;
-/*
- * ======================================================================== Copyright (c) 2005-2015
- * The OPC Foundation, Inc. All rights reserved.
- *
- * OPC Foundation MIT License 1.00
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
- * associated documentation files (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or
- * substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
- * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- *
- * The complete license agreement can be found here: http://opcfoundation.org/License/MIT/1.00/
- * ======================================================================
- */
-
-
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.opcfoundation.ua.application.Application;
-import org.opcfoundation.ua.application.Server;
-import org.opcfoundation.ua.builtintypes.*;
-import org.opcfoundation.ua.common.ServiceFaultException;
-import org.opcfoundation.ua.common.ServiceResultException;
-import org.opcfoundation.ua.core.*;
-import org.opcfoundation.ua.transport.endpoint.EndpointServiceRequest;
-import org.opcfoundation.ua.transport.security.*;
-import org.opcfoundation.ua.utils.CryptoUtil;
-import org.opcfoundation.ua.utils.EndpointUtil;
-import org.opcfoundation.ua.application.Application;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.interfaces.RSAPrivateKey;
-import java.util.Random;
-import java.util.UUID;
-
-
-/**
- * Simple Server example. This server responds to stack test and endpoint discover service requests.
- *
- */
-public class OPCTestPlain {
-
-
-    static class MyAttributeServiceHandler implements AttributeServiceSetHandler {
-
-        @Override
-        public void onHistoryRead(EndpointServiceRequest<HistoryReadRequest, HistoryReadResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onHistoryUpdate(EndpointServiceRequest<HistoryUpdateRequest, HistoryUpdateResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onRead(EndpointServiceRequest<ReadRequest, ReadResponse> req) throws ServiceFaultException {
-            ReadRequest request = req.getRequest();
-            ReadValueId[] nodesToRead = request.getNodesToRead();
-
-            DataValue[] results = new DataValue[nodesToRead.length];
-            for (int i = 0; i < nodesToRead.length; i++) {
-                if (Identifiers.RootFolder.equals(nodesToRead[i].getNodeId())) {
-                    if (Attributes.BrowseName.equals(nodesToRead[i].getAttributeId())) {
-                        results[i] = new DataValue(new Variant(new QualifiedName("Root")));
-                    } else if (Attributes.DisplayName.equals(nodesToRead[i].getAttributeId())) {
-                        results[i] = new DataValue(new Variant(new LocalizedText("Root", LocalizedText.NO_LOCALE)));
-                    } else {
-                        results[i] = new DataValue(new StatusCode(StatusCodes.Bad_AttributeIdInvalid));
-                    }
-                } else {
-                    results[i] = new DataValue(new StatusCode(StatusCodes.Bad_NodeIdUnknown));
-                }
-            }
-            ReadResponse response = new ReadResponse(null, results, null);
-            req.sendResponse(response);
-        }
-
-        @Override
-        public void onWrite(EndpointServiceRequest<WriteRequest, WriteResponse> req) throws ServiceFaultException {
-
-        }
-
-    };
-
-    static class MyNodeManagementServiceHandler implements NodeManagementServiceSetHandler {
-
-        @Override
-        public void onAddNodes(EndpointServiceRequest<AddNodesRequest, AddNodesResponse> req) throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onAddReferences(EndpointServiceRequest<AddReferencesRequest, AddReferencesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onBrowse(EndpointServiceRequest<BrowseRequest, BrowseResponse> req) throws ServiceFaultException {
-            BrowseRequest request = req.getRequest();
-            BrowseResult[] Results = new BrowseResult[request.getNodesToBrowse().length];
-            for (int i = 0; i < request.getNodesToBrowse().length; i++) {
-                StatusCode statusCode;
-                if (Identifiers.RootFolder.equals(request.getNodesToBrowse()[i].getNodeId())) {
-                    statusCode = StatusCode.GOOD;
-                } else {
-                    statusCode = new StatusCode(StatusCodes.Bad_NodeIdUnknown);
-                }
-                Results[i] = new BrowseResult(statusCode, null, null);
-            }
-            BrowseResponse response = new BrowseResponse(null, Results, null);
-            req.sendResponse(response);
-
-        }
-
-        @Override
-        public void onBrowseNext(EndpointServiceRequest<BrowseNextRequest, BrowseNextResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onDeleteNodes(EndpointServiceRequest<DeleteNodesRequest, DeleteNodesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onDeleteReferences(EndpointServiceRequest<DeleteReferencesRequest, DeleteReferencesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onQueryFirst(EndpointServiceRequest<QueryFirstRequest, QueryFirstResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onQueryNext(EndpointServiceRequest<QueryNextRequest, QueryNextResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onRegisterNodes(EndpointServiceRequest<RegisterNodesRequest, RegisterNodesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onTranslateBrowsePathsToNodeIds(
-                EndpointServiceRequest<TranslateBrowsePathsToNodeIdsRequest, TranslateBrowsePathsToNodeIdsResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onUnregisterNodes(EndpointServiceRequest<UnregisterNodesRequest, UnregisterNodesResponse> req)
-                throws ServiceFaultException {
-
-        }
-
-    }
-
-
-    static class MyServerExample extends Server implements SessionServiceSetHandler {
-
-        public MyServerExample(Application application) throws Exception {
-            super(application);
-            addServiceHandler(this);
-
-            // Add User Token Policies
-            addUserTokenPolicy(UserTokenPolicy.ANONYMOUS);
-            addUserTokenPolicy(UserTokenPolicy.SECURE_USERNAME_PASSWORD);
-
-            // Create an endpoint for each network interface
-            String hostname = EndpointUtil.getHostname();
-            String bindAddress, endpointAddress;
-            for (String addr : EndpointUtil.getInetAddressNames()) {
-                bindAddress = "http://" + addr + ":8443/UAExample";
-                endpointAddress = "http://" + hostname + ":8443/UAExample";
-                System.out.println(endpointAddress + " bound at " + bindAddress);
-                // The HTTPS ports are using NONE OPC security
-                bind(bindAddress, endpointAddress, SecurityMode.NONE);
-
-                bindAddress = "opc.tcp://" + addr + ":8666/UAExample";
-                endpointAddress = "opc.tcp://" + hostname + ":8666/UAExample";
-                System.out.println(endpointAddress + " bound at " + bindAddress);
-                bind(bindAddress, endpointAddress, SecurityMode.NONE);
-            }
-
-            //////////////////////////////////////
-        }
-
-        @Override
-        public void onActivateSession(EndpointServiceRequest<ActivateSessionRequest, ActivateSessionResponse> msgExchange)
-                throws ServiceFaultException {
-            ActivateSessionResponse res = new ActivateSessionResponse();
-            res.setServerNonce(CryptoUtil.createNonce(32));
-            res.setResults(new StatusCode[] {StatusCode.GOOD});
-            msgExchange.sendResponse(res);
-        }
-
-        @Override
-        public void onCancel(EndpointServiceRequest<CancelRequest, CancelResponse> msgExchange)
-                throws ServiceFaultException {
-
-        }
-
-        @Override
-        public void onCloseSession(EndpointServiceRequest<CloseSessionRequest, CloseSessionResponse> msgExchange)
-                throws ServiceFaultException {
-            CloseSessionResponse res = new CloseSessionResponse();
-            msgExchange.sendResponse(res);
-        }
-
-        @Override
-        public void onCreateSession(EndpointServiceRequest<CreateSessionRequest, CreateSessionResponse> msgExchange)
-                throws ServiceFaultException {
-            CreateSessionRequest req = msgExchange.getRequest();
-            CreateSessionResponse res = new CreateSessionResponse();
-            byte[] token = new byte[32];
-            byte[] nonce = new byte[32];
-            Random r = new Random();
-            r.nextBytes(nonce);
-            r.nextBytes(token);
-            res.setAuthenticationToken(new NodeId(0, token));
-            EndpointConfiguration endpointConfiguration = EndpointConfiguration.defaults();
-            res.setMaxRequestMessageSize(UnsignedInteger
-                    .valueOf(Math.max(endpointConfiguration.getMaxMessageSize(), req.getMaxResponseMessageSize().longValue())));
-            res.setRevisedSessionTimeout(Math.max(req.getRequestedSessionTimeout(), 60 * 1000));
-            KeyPair cert = getApplication().getApplicationInstanceCertificates()[0];
-            res.setServerCertificate(ByteString.valueOf(cert.getCertificate().getEncoded()));
-            res.setServerEndpoints(this.getEndpointDescriptions());
-            res.setServerNonce(ByteString.valueOf(nonce));
-
-            ByteString clientCertificate = req.getClientCertificate();
-            ByteString clientNonce = req.getClientNonce();
-            SecurityPolicy securityPolicy = msgExchange.getChannel().getSecurityPolicy();
-            res.setServerSignature(
-                    getServerSignature(clientCertificate, clientNonce, securityPolicy, cert.getPrivateKey().getPrivateKey()));
-
-            res.setServerSoftwareCertificates(getApplication().getSoftwareCertificates());
-            res.setSessionId(new NodeId(0, "Session-" + UUID.randomUUID()));
-            msgExchange.sendResponse(res);
-        }
-
-        private SignatureData getServerSignature(ByteString clientCertificate, ByteString clientNonce,
-                                                 SecurityPolicy securityPolicy, final RSAPrivateKey privateKey) throws ServiceFaultException {
-            if (clientCertificate != null) {
-                ByteArrayOutputStream s = new ByteArrayOutputStream();
-                try {
-                    s.write(clientCertificate.getValue());
-                } catch (IOException e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_SecurityChecksFailed));
-                } catch (Exception e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NonceInvalid));
-                }
-                try {
-                    s.write(clientNonce.getValue());
-                } catch (IOException e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NonceInvalid));
-                } catch (Exception e) {
-                    throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NonceInvalid));
-                }
-                try {
-                    SecurityAlgorithm algorithm = securityPolicy.getAsymmetricSignatureAlgorithm();
-                    return new SignatureData(algorithm.getUri(),
-                            ByteString.valueOf(CryptoUtil.getCryptoProvider().signAsymm(privateKey, algorithm, s.toByteArray())));
-
-                } catch (ServiceResultException e) {
-                    throw new ServiceFaultException(e);
-                }
-            }
-            return null;
-        }
-    }
-
-    public static void main(String[] args) throws Exception {
-        CryptoUtil.setSecurityProviderName("SunJCE");
-        ////////////// SERVER //////////////
-        // Create UA Server Application
-        // Create UA Service Server
-        Application myServerApplication = new Application();
-        MyServerExample myServer = new MyServerExample(myServerApplication);
-
-        myServer.addServiceHandler(new MyNodeManagementServiceHandler());
-        myServer.addServiceHandler(new MyAttributeServiceHandler());
-
-        //////////////////////////////////////
-        // Press enter to shutdown
-        System.out.println("Press enter to shutdown");
-        System.in.read();
-        //////////////////////////////////////
-
-
-        ///////////// SHUTDOWN /////////////
-        // Close the server by unbinding all endpoints
-        myServer.getApplication().close();
-        //////////////////////////////////////
-
-    }
-
-}

+ 110 - 0
src/main/java/at/acdp/opcur/opc/CustomDataType.java

@@ -0,0 +1,110 @@
+package at.acdp.opcur.opc;
+
+/*
+ * Copyright (c) 2017 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.eclipse.milo.opcua.stack.core.UaSerializationException;
+import org.eclipse.milo.opcua.stack.core.serialization.UaDecoder;
+import org.eclipse.milo.opcua.stack.core.serialization.UaEncoder;
+import org.eclipse.milo.opcua.stack.core.serialization.codecs.GenericDataTypeCodec;
+import org.eclipse.milo.opcua.stack.core.serialization.codecs.SerializationContext;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+
+public class CustomDataType {
+
+    private final String foo;
+    private final UInteger bar;
+    private final boolean baz;
+
+    public CustomDataType() {
+        this(null, uint(0), false);
+    }
+
+    public CustomDataType(String foo, UInteger bar, boolean baz) {
+        this.foo = foo;
+        this.bar = bar;
+        this.baz = baz;
+    }
+
+    public String getFoo() {
+        return foo;
+    }
+
+    public UInteger getBar() {
+        return bar;
+    }
+
+    public boolean isBaz() {
+        return baz;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        CustomDataType that = (CustomDataType) o;
+        return baz == that.baz &&
+                Objects.equal(foo, that.foo) &&
+                Objects.equal(bar, that.bar);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(foo, bar, baz);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("foo", foo)
+                .add("bar", bar)
+                .add("baz", baz)
+                .toString();
+    }
+
+    public static class Codec extends GenericDataTypeCodec<CustomDataType> {
+        @Override
+        public Class<CustomDataType> getType() {
+            return CustomDataType.class;
+        }
+
+        @Override
+        public CustomDataType decode(
+                SerializationContext context,
+                UaDecoder decoder) throws UaSerializationException {
+
+            String foo = decoder.readString("Foo");
+            UInteger bar = decoder.readUInt32("Bar");
+            boolean baz = decoder.readBoolean("Baz");
+
+            return new CustomDataType(foo, bar, baz);
+        }
+
+        @Override
+        public void encode(
+                SerializationContext context,
+                CustomDataType customDataType,
+                UaEncoder encoder) throws UaSerializationException {
+
+            encoder.writeString("Foo", customDataType.foo);
+            encoder.writeUInt32("Bar", customDataType.bar);
+            encoder.writeBoolean("Baz", customDataType.baz);
+        }
+    }
+
+}

+ 119 - 0
src/main/java/at/acdp/opcur/opc/KeyStoreLoader.java

@@ -0,0 +1,119 @@
+package at.acdp.opcur.opc;
+
+/*
+ * Copyright (c) 2016 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class KeyStoreLoader {
+
+    private static final Pattern IP_ADDR_PATTERN = Pattern.compile(
+            "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
+
+    private static final String SERVER_ALIAS = "server-ai";
+    private static final char[] PASSWORD = "password".toCharArray();
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private X509Certificate[] serverCertificateChain;
+    private X509Certificate serverCertificate;
+    private KeyPair serverKeyPair;
+
+    KeyStoreLoader load(File baseDir) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+
+        File serverKeyStore = baseDir.toPath().resolve("example-server.pfx").toFile();
+
+        logger.info("Loading KeyStore at {}", serverKeyStore);
+
+        if (!serverKeyStore.exists()) {
+            keyStore.load(null, PASSWORD);
+
+            KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
+
+            String applicationUri = "urn:eclipse:milo:examples:server:" + UUID.randomUUID();
+
+            SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
+                    .setCommonName("Eclipse Milo Example Server")
+                    .setOrganization("digitalpetri")
+                    .setOrganizationalUnit("dev")
+                    .setLocalityName("Folsom")
+                    .setStateName("CA")
+                    .setCountryCode("US")
+                    .setApplicationUri(applicationUri)
+                    .addDnsName("localhost")
+                    .addIpAddress("127.0.0.1");
+
+            // Get as many hostnames and IP addresses as we can listed in the certificate.
+            for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
+                if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
+                    builder.addIpAddress(hostname);
+                } else {
+                    builder.addDnsName(hostname);
+                }
+            }
+
+            X509Certificate certificate = builder.build();
+
+            keyStore.setKeyEntry(SERVER_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});
+            keyStore.store(new FileOutputStream(serverKeyStore), PASSWORD);
+        } else {
+            keyStore.load(new FileInputStream(serverKeyStore), PASSWORD);
+        }
+
+        Key serverPrivateKey = keyStore.getKey(SERVER_ALIAS, PASSWORD);
+        if (serverPrivateKey instanceof PrivateKey) {
+            serverCertificate = (X509Certificate) keyStore.getCertificate(SERVER_ALIAS);
+
+            serverCertificateChain = Arrays.stream(keyStore.getCertificateChain(SERVER_ALIAS))
+                    .map(X509Certificate.class::cast)
+                    .toArray(X509Certificate[]::new);
+
+            PublicKey serverPublicKey = serverCertificate.getPublicKey();
+            serverKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);
+        }
+
+        return this;
+    }
+
+    X509Certificate getServerCertificate() {
+        return serverCertificate;
+    }
+
+    public X509Certificate[] getServerCertificateChain() {
+        return serverCertificateChain;
+    }
+
+    KeyPair getServerKeyPair() {
+        return serverKeyPair;
+    }
+
+}

+ 850 - 0
src/main/java/at/acdp/opcur/opc/MyNamespace.java

@@ -0,0 +1,850 @@
+package at.acdp.opcur.opc;
+/*
+ * Copyright (c) 2016 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+
+import java.lang.reflect.Array;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+import com.google.common.collect.Lists;
+import org.eclipse.milo.opcua.sdk.core.AccessLevel;
+import org.eclipse.milo.opcua.sdk.core.Reference;
+import org.eclipse.milo.opcua.sdk.core.ValueRank;
+import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
+import org.eclipse.milo.opcua.sdk.server.api.AccessContext;
+import org.eclipse.milo.opcua.sdk.server.api.DataItem;
+import org.eclipse.milo.opcua.sdk.server.api.MethodInvocationHandler;
+import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;
+import org.eclipse.milo.opcua.sdk.server.api.Namespace;
+import org.eclipse.milo.opcua.sdk.server.api.nodes.VariableNode;
+import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext;
+import org.eclipse.milo.opcua.sdk.server.nodes.NodeFactory;
+import org.eclipse.milo.opcua.sdk.server.nodes.ServerNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaDataTypeNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectTypeNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.delegates.AttributeDelegate;
+import org.eclipse.milo.opcua.sdk.server.nodes.delegates.AttributeDelegateChain;
+import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler;
+import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel;
+import org.eclipse.milo.opcua.stack.core.AttributeId;
+import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.stack.core.StatusCodes;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.OpcUaBinaryDataTypeDictionary;
+import org.eclipse.milo.opcua.stack.core.types.OpcUaDataTypeManager;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
+import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
+import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.Range;
+import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
+import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue;
+import org.eclipse.milo.opcua.stack.core.util.FutureUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte;
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ulong;
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;
+
+public class MyNamespace implements Namespace {
+
+    public static final String NAMESPACE_URI = "urn:eclipse:milo:hello-world";
+
+    private static final Object[][] STATIC_SCALAR_NODES = new Object[][]{
+            {"Boolean", Identifiers.Boolean, new Variant(false)},
+            {"Byte", Identifiers.Byte, new Variant(ubyte(0x00))},
+            {"SByte", Identifiers.SByte, new Variant((byte) 0x00)},
+            {"Integer", Identifiers.Integer, new Variant(32)},
+            {"Int16", Identifiers.Int16, new Variant((short) 16)},
+            {"Int32", Identifiers.Int32, new Variant(32)},
+            {"Int64", Identifiers.Int64, new Variant(64L)},
+            {"UInteger", Identifiers.UInteger, new Variant(uint(32))},
+            {"UInt16", Identifiers.UInt16, new Variant(ushort(16))},
+            {"UInt32", Identifiers.UInt32, new Variant(uint(32))},
+            {"UInt64", Identifiers.UInt64, new Variant(ulong(64L))},
+            {"Float", Identifiers.Float, new Variant(3.14f)},
+            {"Double", Identifiers.Double, new Variant(3.14d)},
+            {"String", Identifiers.String, new Variant("string value")},
+            {"DateTime", Identifiers.DateTime, new Variant(DateTime.now())},
+            {"Guid", Identifiers.Guid, new Variant(UUID.randomUUID())},
+            {"ByteString", Identifiers.ByteString, new Variant(new ByteString(new byte[]{0x01, 0x02, 0x03, 0x04}))},
+            {"XmlElement", Identifiers.XmlElement, new Variant(new XmlElement("<a>hello</a>"))},
+            {"LocalizedText", Identifiers.LocalizedText, new Variant(LocalizedText.english("localized text"))},
+            {"QualifiedName", Identifiers.QualifiedName, new Variant(new QualifiedName(1234, "defg"))},
+            {"NodeId", Identifiers.NodeId, new Variant(new NodeId(1234, "abcd"))},
+
+            {"Duration", Identifiers.Duration, new Variant(1.0)},
+            {"UtcTime", Identifiers.UtcTime, new Variant(DateTime.now())},
+    };
+
+    private static final Object[][] STATIC_ARRAY_NODES = new Object[][]{
+            {"BooleanArray", Identifiers.Boolean, false},
+            {"ByteArray", Identifiers.Byte, ubyte(0)},
+            {"SByteArray", Identifiers.SByte, (byte) 0x00},
+            {"Int16Array", Identifiers.Int16, (short) 16},
+            {"Int32Array", Identifiers.Int32, 32},
+            {"Int64Array", Identifiers.Int64, 64L},
+            {"UInt16Array", Identifiers.UInt16, ushort(16)},
+            {"UInt32Array", Identifiers.UInt32, uint(32)},
+            {"UInt64Array", Identifiers.UInt64, ulong(64L)},
+            {"FloatArray", Identifiers.Float, 3.14f},
+            {"DoubleArray", Identifiers.Double, 3.14d},
+            {"StringArray", Identifiers.String, "string value"},
+            {"DateTimeArray", Identifiers.DateTime, DateTime.now()},
+            {"GuidArray", Identifiers.Guid, UUID.randomUUID()},
+            {"ByteStringArray", Identifiers.ByteString, new ByteString(new byte[]{0x01, 0x02, 0x03, 0x04})},
+            {"XmlElementArray", Identifiers.XmlElement, new XmlElement("<a>hello</a>")},
+            {"LocalizedTextArray", Identifiers.LocalizedText, LocalizedText.english("localized text")},
+            {"QualifiedNameArray", Identifiers.QualifiedName, new QualifiedName(1234, "defg")},
+            {"NodeIdArray", Identifiers.NodeId, new NodeId(1234, "abcd")}
+    };
+
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Random random = new Random();
+
+    private final SubscriptionModel subscriptionModel;
+
+    private final NodeFactory nodeFactory;
+
+    private final OpcUaServer server;
+    private final UShort namespaceIndex;
+
+    public MyNamespace(OpcUaServer server, UShort namespaceIndex) {
+        this.server = server;
+        this.namespaceIndex = namespaceIndex;
+
+        subscriptionModel = new SubscriptionModel(server, this);
+
+        nodeFactory = new NodeFactory(
+                server.getNodeMap(),
+                server.getObjectTypeManager(),
+                server.getVariableTypeManager()
+        );
+
+        try {
+            // Create a "HelloWorld" folder and add it to the node manager
+            NodeId folderNodeId = new NodeId(namespaceIndex, "HelloWorld");
+
+            UaFolderNode folderNode = new UaFolderNode(
+                    server.getNodeMap(),
+                    folderNodeId,
+                    new QualifiedName(namespaceIndex, "HelloWorld"),
+                    LocalizedText.english("HelloWorld")
+            );
+
+            server.getNodeMap().addNode(folderNode);
+
+            // Make sure our new folder shows up under the server's Objects folder
+            server.getUaNamespace().addReference(
+                    Identifiers.ObjectsFolder,
+                    Identifiers.Organizes,
+                    true,
+                    folderNodeId.expanded(),
+                    NodeClass.Object
+            );
+
+            // Add the rest of the nodes
+            addVariableNodes(folderNode);
+
+            addMethodNode(folderNode);
+
+            addCustomDataTypeVariable(folderNode);
+
+            addCustomObjectTypeAndInstance(folderNode);
+        } catch (UaException e) {
+            logger.error("Error adding nodes: {}", e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public UShort getNamespaceIndex() {
+        return namespaceIndex;
+    }
+
+    @Override
+    public String getNamespaceUri() {
+        return NAMESPACE_URI;
+    }
+
+    private void addVariableNodes(UaFolderNode rootNode) {
+        addArrayNodes(rootNode);
+        addScalarNodes(rootNode);
+        addAdminReadableNodes(rootNode);
+        addAdminWritableNodes(rootNode);
+        addDynamicNodes(rootNode);
+        addDataAccessNodes(rootNode);
+        addWriteOnlyNodes(rootNode);
+    }
+
+    private void addArrayNodes(UaFolderNode rootNode) {
+        UaFolderNode arrayTypesFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/ArrayTypes"),
+                new QualifiedName(namespaceIndex, "ArrayTypes"),
+                LocalizedText.english("ArrayTypes")
+        );
+
+        server.getNodeMap().addNode(arrayTypesFolder);
+        rootNode.addOrganizes(arrayTypesFolder);
+
+        for (Object[] os : STATIC_ARRAY_NODES) {
+            String name = (String) os[0];
+            NodeId typeId = (NodeId) os[1];
+            Object value = os[2];
+            Object array = Array.newInstance(value.getClass(), 5);
+            for (int i = 0; i < 5; i++) {
+                Array.set(array, i, value);
+            }
+            Variant variant = new Variant(array);
+
+            UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                    .setNodeId(new NodeId(namespaceIndex, "HelloWorld/ArrayTypes/" + name))
+                    .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setBrowseName(new QualifiedName(namespaceIndex, name))
+                    .setDisplayName(LocalizedText.english(name))
+                    .setDataType(typeId)
+                    .setTypeDefinition(Identifiers.BaseDataVariableType)
+                    .setValueRank(ValueRank.OneDimension.getValue())
+                    .setArrayDimensions(new UInteger[]{uint(0)})
+                    .build();
+
+            node.setValue(new DataValue(variant));
+
+            node.setAttributeDelegate(new ValueLoggingDelegate());
+
+            server.getNodeMap().addNode(node);
+            arrayTypesFolder.addOrganizes(node);
+        }
+    }
+
+    private void addScalarNodes(UaFolderNode rootNode) {
+        UaFolderNode scalarTypesFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/ScalarTypes"),
+                new QualifiedName(namespaceIndex, "ScalarTypes"),
+                LocalizedText.english("ScalarTypes")
+        );
+
+        server.getNodeMap().addNode(scalarTypesFolder);
+        rootNode.addOrganizes(scalarTypesFolder);
+
+        for (Object[] os : STATIC_SCALAR_NODES) {
+            String name = (String) os[0];
+            NodeId typeId = (NodeId) os[1];
+            Variant variant = (Variant) os[2];
+
+            UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                    .setNodeId(new NodeId(namespaceIndex, "HelloWorld/ScalarTypes/" + name))
+                    .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setBrowseName(new QualifiedName(namespaceIndex, name))
+                    .setDisplayName(LocalizedText.english(name))
+                    .setDataType(typeId)
+                    .setTypeDefinition(Identifiers.BaseDataVariableType)
+                    .build();
+
+            node.setValue(new DataValue(variant));
+
+            node.setAttributeDelegate(new ValueLoggingDelegate());
+
+            server.getNodeMap().addNode(node);
+            scalarTypesFolder.addOrganizes(node);
+        }
+    }
+
+    private void addWriteOnlyNodes(UaFolderNode rootNode) {
+        UaFolderNode writeOnlyFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/WriteOnly"),
+                new QualifiedName(namespaceIndex, "WriteOnly"),
+                LocalizedText.english("WriteOnly")
+        );
+
+        server.getNodeMap().addNode(writeOnlyFolder);
+        rootNode.addOrganizes(writeOnlyFolder);
+
+        String name = "String";
+        UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "HelloWorld/WriteOnly/" + name))
+                .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.WRITE_ONLY)))
+                .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.WRITE_ONLY)))
+                .setBrowseName(new QualifiedName(namespaceIndex, name))
+                .setDisplayName(LocalizedText.english(name))
+                .setDataType(Identifiers.String)
+                .setTypeDefinition(Identifiers.BaseDataVariableType)
+                .build();
+
+        node.setValue(new DataValue(new Variant("can't read this")));
+
+        server.getNodeMap().addNode(node);
+        writeOnlyFolder.addOrganizes(node);
+    }
+
+    private void addAdminReadableNodes(UaFolderNode rootNode) {
+        UaFolderNode adminFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanRead"),
+                new QualifiedName(namespaceIndex, "OnlyAdminCanRead"),
+                LocalizedText.english("OnlyAdminCanRead")
+        );
+
+        server.getNodeMap().addNode(adminFolder);
+        rootNode.addOrganizes(adminFolder);
+
+        String name = "String";
+        UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanRead/" + name))
+                .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                .setBrowseName(new QualifiedName(namespaceIndex, name))
+                .setDisplayName(LocalizedText.english(name))
+                .setDataType(Identifiers.String)
+                .setTypeDefinition(Identifiers.BaseDataVariableType)
+                .build();
+
+        node.setValue(new DataValue(new Variant("shh... don't tell the lusers")));
+
+        node.setAttributeDelegate(new RestrictedAccessDelegate(identity -> {
+            if ("admin".equals(identity)) {
+                return AccessLevel.READ_WRITE;
+            } else {
+                return AccessLevel.NONE;
+            }
+        }));
+
+        server.getNodeMap().addNode(node);
+        adminFolder.addOrganizes(node);
+    }
+
+    private void addAdminWritableNodes(UaFolderNode rootNode) {
+        UaFolderNode adminFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanWrite"),
+                new QualifiedName(namespaceIndex, "OnlyAdminCanWrite"),
+                LocalizedText.english("OnlyAdminCanWrite")
+        );
+
+        server.getNodeMap().addNode(adminFolder);
+        rootNode.addOrganizes(adminFolder);
+
+        String name = "String";
+        UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanWrite/" + name))
+                .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                .setBrowseName(new QualifiedName(namespaceIndex, name))
+                .setDisplayName(LocalizedText.english(name))
+                .setDataType(Identifiers.String)
+                .setTypeDefinition(Identifiers.BaseDataVariableType)
+                .build();
+
+        node.setValue(new DataValue(new Variant("admin was here")));
+
+        node.setAttributeDelegate(new RestrictedAccessDelegate(identity -> {
+            if ("admin".equals(identity)) {
+                return AccessLevel.READ_WRITE;
+            } else {
+                return AccessLevel.READ_ONLY;
+            }
+        }));
+
+        server.getNodeMap().addNode(node);
+        adminFolder.addOrganizes(node);
+    }
+
+    private void addDynamicNodes(UaFolderNode rootNode) {
+        UaFolderNode dynamicFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/Dynamic"),
+                new QualifiedName(namespaceIndex, "Dynamic"),
+                LocalizedText.english("Dynamic")
+        );
+
+        server.getNodeMap().addNode(dynamicFolder);
+        rootNode.addOrganizes(dynamicFolder);
+
+        // Dynamic Boolean
+        {
+            String name = "Boolean";
+            NodeId typeId = Identifiers.Boolean;
+            Variant variant = new Variant(false);
+
+            UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                    .setNodeId(new NodeId(namespaceIndex, "HelloWorld/Dynamic/" + name))
+                    .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setBrowseName(new QualifiedName(namespaceIndex, name))
+                    .setDisplayName(LocalizedText.english(name))
+                    .setDataType(typeId)
+                    .setTypeDefinition(Identifiers.BaseDataVariableType)
+                    .build();
+
+            node.setValue(new DataValue(variant));
+
+            AttributeDelegate delegate = AttributeDelegateChain.create(
+                    new AttributeDelegate() {
+                        @Override
+                        public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
+                            return new DataValue(new Variant(random.nextBoolean()));
+                        }
+                    },
+                    ValueLoggingDelegate::new
+            );
+
+            node.setAttributeDelegate(delegate);
+
+            server.getNodeMap().addNode(node);
+            dynamicFolder.addOrganizes(node);
+        }
+
+        // Dynamic Int32
+        {
+            String name = "Int32";
+            NodeId typeId = Identifiers.Int32;
+            Variant variant = new Variant(0);
+
+            UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                    .setNodeId(new NodeId(namespaceIndex, "HelloWorld/Dynamic/" + name))
+                    .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setBrowseName(new QualifiedName(namespaceIndex, name))
+                    .setDisplayName(LocalizedText.english(name))
+                    .setDataType(typeId)
+                    .setTypeDefinition(Identifiers.BaseDataVariableType)
+                    .build();
+
+            node.setValue(new DataValue(variant));
+
+            AttributeDelegate delegate = AttributeDelegateChain.create(
+                    new AttributeDelegate() {
+                        @Override
+                        public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
+                            return new DataValue(new Variant(random.nextInt()));
+                        }
+                    },
+                    ValueLoggingDelegate::new
+            );
+
+            node.setAttributeDelegate(delegate);
+
+            server.getNodeMap().addNode(node);
+            dynamicFolder.addOrganizes(node);
+        }
+
+        // Dynamic Double
+        {
+            String name = "Double";
+            NodeId typeId = Identifiers.Double;
+            Variant variant = new Variant(0.0);
+
+            UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
+                    .setNodeId(new NodeId(namespaceIndex, "HelloWorld/Dynamic/" + name))
+                    .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                    .setBrowseName(new QualifiedName(namespaceIndex, name))
+                    .setDisplayName(LocalizedText.english(name))
+                    .setDataType(typeId)
+                    .setTypeDefinition(Identifiers.BaseDataVariableType)
+                    .build();
+
+            node.setValue(new DataValue(variant));
+
+            AttributeDelegate delegate = AttributeDelegateChain.create(
+                    new AttributeDelegate() {
+                        @Override
+                        public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
+                            return new DataValue(new Variant(random.nextDouble()));
+                        }
+                    },
+                    ValueLoggingDelegate::new
+            );
+
+            node.setAttributeDelegate(delegate);
+
+            server.getNodeMap().addNode(node);
+            dynamicFolder.addOrganizes(node);
+        }
+    }
+
+    private void addDataAccessNodes(UaFolderNode rootNode) {
+        // DataAccess folder
+        UaFolderNode dataAccessFolder = new UaFolderNode(
+                server.getNodeMap(),
+                new NodeId(namespaceIndex, "HelloWorld/DataAccess"),
+                new QualifiedName(namespaceIndex, "DataAccess"),
+                LocalizedText.english("DataAccess")
+        );
+
+        server.getNodeMap().addNode(dataAccessFolder);
+        rootNode.addOrganizes(dataAccessFolder);
+
+        // AnalogItemType node
+        AnalogItemNode node = nodeFactory.createVariable(
+                new NodeId(namespaceIndex, "HelloWorld/DataAccess/AnalogValue"),
+                new QualifiedName(namespaceIndex, "AnalogValue"),
+                LocalizedText.english("AnalogValue"),
+                Identifiers.AnalogItemType,
+                AnalogItemNode.class
+        );
+
+        node.setDataType(Identifiers.Double);
+        node.setValue(new DataValue(new Variant(3.14d)));
+
+        node.setEURange(new Range(0.0, 100.0));
+
+        server.getNodeMap().addNode(node);
+        dataAccessFolder.addOrganizes(node);
+    }
+
+    private void addMethodNode(UaFolderNode folderNode) {
+        UaMethodNode methodNode = UaMethodNode.builder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "HelloWorld/sqrt(x)"))
+                .setBrowseName(new QualifiedName(namespaceIndex, "sqrt(x)"))
+                .setDisplayName(new LocalizedText(null, "sqrt(x)"))
+                .setDescription(
+                        LocalizedText.english("Returns the correctly rounded positive square root of a double value."))
+                .build();
+
+
+        try {
+            AnnotationBasedInvocationHandler invocationHandler =
+                    AnnotationBasedInvocationHandler.fromAnnotatedObject(
+                            server.getNodeMap(), new SqrtMethod());
+
+            methodNode.setProperty(UaMethodNode.InputArguments, invocationHandler.getInputArguments());
+            methodNode.setProperty(UaMethodNode.OutputArguments, invocationHandler.getOutputArguments());
+            methodNode.setInvocationHandler(invocationHandler);
+
+            server.getNodeMap().addNode(methodNode);
+
+            folderNode.addReference(new Reference(
+                    folderNode.getNodeId(),
+                    Identifiers.HasComponent,
+                    methodNode.getNodeId().expanded(),
+                    methodNode.getNodeClass(),
+                    true
+            ));
+
+            methodNode.addReference(new Reference(
+                    methodNode.getNodeId(),
+                    Identifiers.HasComponent,
+                    folderNode.getNodeId().expanded(),
+                    folderNode.getNodeClass(),
+                    false
+            ));
+        } catch (Exception e) {
+            logger.error("Error creating sqrt() method.", e);
+        }
+    }
+
+    private void addCustomObjectTypeAndInstance(UaFolderNode rootFolder) throws UaException {
+        // Define a new ObjectType called "MyObjectType".
+        UaObjectTypeNode objectTypeNode = UaObjectTypeNode.builder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "ObjectTypes/MyObjectType"))
+                .setBrowseName(new QualifiedName(namespaceIndex, "MyObjectType"))
+                .setDisplayName(LocalizedText.english("MyObjectType"))
+                .setIsAbstract(false)
+                .build();
+
+        // "Foo" and "Bar" are members. These nodes are what are called "instance declarations" by the spec.
+        UaVariableNode foo = UaVariableNode.builder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "ObjectTypes/MyObjectType.Foo"))
+                .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                .setBrowseName(new QualifiedName(namespaceIndex, "Foo"))
+                .setDisplayName(LocalizedText.english("Foo"))
+                .setDataType(Identifiers.Int16)
+                .setTypeDefinition(Identifiers.BaseDataVariableType)
+                .build();
+
+        foo.setValue(new DataValue(new Variant(0)));
+        objectTypeNode.addComponent(foo);
+
+        UaVariableNode bar = UaVariableNode.builder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "ObjectTypes/MyObjectType.Bar"))
+                .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                .setBrowseName(new QualifiedName(namespaceIndex, "Bar"))
+                .setDisplayName(LocalizedText.english("Bar"))
+                .setDataType(Identifiers.String)
+                .setTypeDefinition(Identifiers.BaseDataVariableType)
+                .build();
+
+        bar.setValue(new DataValue(new Variant("bar")));
+        objectTypeNode.addComponent(bar);
+
+        // Tell the ObjectTypeManager about our new type.
+        // This let's us use NodeFactory to instantiate instances of the type.
+        server.getObjectTypeManager().registerObjectType(
+                objectTypeNode.getNodeId(),
+                UaObjectNode.class,
+                UaObjectNode::new
+        );
+
+        // Add our ObjectTypeNode as a subtype of BaseObjectType.
+        server.getUaNamespace().addReference(
+                Identifiers.BaseObjectType,
+                Identifiers.HasSubtype,
+                true,
+                objectTypeNode.getNodeId().expanded(),
+                NodeClass.ObjectType
+        );
+
+        // Add the inverse SubtypeOf relationship.
+        objectTypeNode.addReference(new Reference(
+                objectTypeNode.getNodeId(),
+                Identifiers.HasSubtype,
+                Identifiers.BaseObjectType.expanded(),
+                NodeClass.ObjectType,
+                false
+        ));
+
+        // Add it into the address space.
+        server.getNodeMap().addNode(objectTypeNode);
+
+        // Use NodeFactory to create instance of MyObjectType called "MyObject".
+        // NodeFactory takes care of recursively instantiating MyObject member nodes
+        // as well as adding all nodes to the address space.
+        UaObjectNode myObject = nodeFactory.createObject(
+                new NodeId(namespaceIndex, "HelloWorld/MyObject"),
+                new QualifiedName(namespaceIndex, "MyObject"),
+                LocalizedText.english("MyObject"),
+                objectTypeNode.getNodeId()
+        );
+
+        // Add forward and inverse references from the root folder.
+        rootFolder.addOrganizes(myObject);
+
+        myObject.addReference(new Reference(
+                myObject.getNodeId(),
+                Identifiers.Organizes,
+                rootFolder.getNodeId().expanded(),
+                rootFolder.getNodeClass(),
+                false
+        ));
+    }
+
+    private void addCustomDataTypeVariable(UaFolderNode rootFolder) {
+        // add a custom DataTypeNode as a subtype of the built-in Structure DataTypeNode
+        NodeId dataTypeId = new NodeId(namespaceIndex, "DataType.CustomDataType");
+
+        UaDataTypeNode dataTypeNode = new UaDataTypeNode(
+                server.getNodeMap(),
+                dataTypeId,
+                new QualifiedName(namespaceIndex, "CustomDataType"),
+                LocalizedText.english("CustomDataType"),
+                LocalizedText.english("CustomDataType"),
+                uint(0),
+                uint(0),
+                false
+        );
+
+        // Inverse ref to Structure
+        dataTypeNode.addReference(new Reference(
+                dataTypeId,
+                Identifiers.HasSubtype,
+                Identifiers.Structure.expanded(),
+                NodeClass.DataType,
+                false
+        ));
+
+        // Forward ref from Structure
+        Optional<UaDataTypeNode> structureDataTypeNode = server.getNodeMap()
+                .getNode(Identifiers.Structure)
+                .map(UaDataTypeNode.class::cast);
+
+        structureDataTypeNode.ifPresent(node ->
+                node.addReference(new Reference(
+                        node.getNodeId(),
+                        Identifiers.HasSubtype,
+                        dataTypeId.expanded(),
+                        NodeClass.DataType,
+                        true
+                ))
+        );
+
+        // Create a dictionary, binaryEncodingId, and register the codec under that id
+        OpcUaBinaryDataTypeDictionary dictionary = new OpcUaBinaryDataTypeDictionary(
+                "urn:eclipse:milo:example:custom-data-type"
+        );
+
+        NodeId binaryEncodingId = new NodeId(namespaceIndex, "DataType.CustomDataType.BinaryEncoding");
+
+        dictionary.registerStructCodec(
+                new CustomDataType.Codec().asBinaryCodec(),
+                "CustomDataType",
+                binaryEncodingId
+        );
+
+        // Register dictionary with the shared DataTypeManager instance
+        OpcUaDataTypeManager.getInstance().registerTypeDictionary(dictionary);
+
+
+        UaVariableNode customDataTypeVariable = UaVariableNode.builder(server.getNodeMap())
+                .setNodeId(new NodeId(namespaceIndex, "HelloWorld/CustomDataTypeVariable"))
+                .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
+                .setBrowseName(new QualifiedName(namespaceIndex, "CustomDataTypeVariable"))
+                .setDisplayName(LocalizedText.english("CustomDataTypeVariable"))
+                .setDataType(dataTypeId)
+                .setTypeDefinition(Identifiers.BaseDataVariableType)
+                .build();
+
+        CustomDataType value = new CustomDataType(
+                "foo",
+                uint(42),
+                true
+        );
+
+        ExtensionObject xo = ExtensionObject.encode(value, binaryEncodingId);
+
+        customDataTypeVariable.setValue(new DataValue(new Variant(xo)));
+
+        rootFolder.addOrganizes(customDataTypeVariable);
+
+        customDataTypeVariable.addReference(new Reference(
+                customDataTypeVariable.getNodeId(),
+                Identifiers.Organizes,
+                rootFolder.getNodeId().expanded(),
+                rootFolder.getNodeClass(),
+                false
+        ));
+    }
+
+    @Override
+    public CompletableFuture<List<Reference>> browse(AccessContext context, NodeId nodeId) {
+        ServerNode node = server.getNodeMap().get(nodeId);
+
+        if (node != null) {
+            return CompletableFuture.completedFuture(node.getReferences());
+        } else {
+            return FutureUtils.failedFuture(new UaException(StatusCodes.Bad_NodeIdUnknown));
+        }
+    }
+
+    @Override
+    public void read(
+            ReadContext context,
+            Double maxAge,
+            TimestampsToReturn timestamps,
+            List<ReadValueId> readValueIds) {
+
+        List<DataValue> results = Lists.newArrayListWithCapacity(readValueIds.size());
+
+        for (ReadValueId readValueId : readValueIds) {
+            ServerNode node = server.getNodeMap().get(readValueId.getNodeId());
+
+            if (node != null) {
+                DataValue value = node.readAttribute(
+                        new AttributeContext(context),
+                        readValueId.getAttributeId(),
+                        timestamps,
+                        readValueId.getIndexRange(),
+                        readValueId.getDataEncoding()
+                );
+
+                results.add(value);
+            } else {
+                results.add(new DataValue(StatusCodes.Bad_NodeIdUnknown));
+            }
+        }
+
+        context.complete(results);
+    }
+
+    @Override
+    public void write(WriteContext context, List<WriteValue> writeValues) {
+        List<StatusCode> results = Lists.newArrayListWithCapacity(writeValues.size());
+
+        for (WriteValue writeValue : writeValues) {
+            ServerNode node = server.getNodeMap().get(writeValue.getNodeId());
+
+            if (node != null) {
+                try {
+                    node.writeAttribute(
+                            new AttributeContext(context),
+                            writeValue.getAttributeId(),
+                            writeValue.getValue(),
+                            writeValue.getIndexRange()
+                    );
+
+                    results.add(StatusCode.GOOD);
+
+                    logger.info(
+                            "Wrote value {} to {} attribute of {}",
+                            writeValue.getValue().getValue(),
+                            AttributeId.from(writeValue.getAttributeId()).map(Object::toString).orElse("unknown"),
+                            node.getNodeId());
+                } catch (UaException e) {
+                    logger.error("Unable to write value={}", writeValue.getValue(), e);
+                    results.add(e.getStatusCode());
+                }
+            } else {
+                results.add(new StatusCode(StatusCodes.Bad_NodeIdUnknown));
+            }
+        }
+
+        context.complete(results);
+    }
+
+    @Override
+    public void onDataItemsCreated(List<DataItem> dataItems) {
+        subscriptionModel.onDataItemsCreated(dataItems);
+    }
+
+    @Override
+    public void onDataItemsModified(List<DataItem> dataItems) {
+        subscriptionModel.onDataItemsModified(dataItems);
+    }
+
+    @Override
+    public void onDataItemsDeleted(List<DataItem> dataItems) {
+        subscriptionModel.onDataItemsDeleted(dataItems);
+    }
+
+    @Override
+    public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) {
+        subscriptionModel.onMonitoringModeChanged(monitoredItems);
+    }
+
+    @Override
+    public Optional<MethodInvocationHandler> getInvocationHandler(NodeId methodId) {
+        Optional<ServerNode> node = server.getNodeMap().getNode(methodId);
+
+        return node.flatMap(n -> {
+            if (n instanceof UaMethodNode) {
+                return ((UaMethodNode) n).getInvocationHandler();
+            } else {
+                return Optional.empty();
+            }
+        });
+    }
+
+}

+ 61 - 0
src/main/java/at/acdp/opcur/opc/RestrictedAccessDelegate.java

@@ -0,0 +1,61 @@
+package at.acdp.opcur.opc;
+
+/*
+ * Copyright (c) 2016 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.eclipse.milo.opcua.sdk.core.AccessLevel;
+import org.eclipse.milo.opcua.sdk.server.Session;
+import org.eclipse.milo.opcua.sdk.server.api.nodes.VariableNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext;
+import org.eclipse.milo.opcua.sdk.server.nodes.delegates.AttributeDelegate;
+import org.eclipse.milo.opcua.sdk.server.nodes.delegates.DelegatingAttributeDelegate;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte;
+
+public class RestrictedAccessDelegate extends DelegatingAttributeDelegate {
+
+    private static final Set<AccessLevel> INTERNAL_ACCESS = AccessLevel.READ_WRITE;
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Function<Object, Set<AccessLevel>> accessLevelsFn;
+
+    public RestrictedAccessDelegate(Function<Object, Set<AccessLevel>> accessLevelsFn) {
+        this(null, accessLevelsFn);
+    }
+
+    public RestrictedAccessDelegate(AttributeDelegate parent, Function<Object, Set<AccessLevel>> accessLevelsFn) {
+        super(parent);
+
+        this.accessLevelsFn = accessLevelsFn;
+    }
+
+    @Override
+    public UByte getUserAccessLevel(AttributeContext context, VariableNode node) throws UaException {
+        Optional<Object> identity = context.getSession().map(Session::getIdentityObject);
+
+        Set<AccessLevel> accessLevels = identity.map(accessLevelsFn).orElse(INTERNAL_ACCESS);
+
+        return ubyte(AccessLevel.getMask(accessLevels));
+    }
+
+}

+ 170 - 0
src/main/java/at/acdp/opcur/opc/Server.java

@@ -0,0 +1,170 @@
+package at.acdp.opcur.opc;
+
+/*
+ * Copyright (c) 2016 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+
+import java.io.File;
+import java.security.Security;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+import com.google.common.collect.ImmutableList;
+import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
+import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
+import org.eclipse.milo.opcua.sdk.server.identity.CompositeValidator;
+import org.eclipse.milo.opcua.sdk.server.identity.UsernameIdentityValidator;
+import org.eclipse.milo.opcua.sdk.server.identity.X509IdentityValidator;
+import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
+import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateManager;
+import org.eclipse.milo.opcua.stack.core.application.DirectoryCertificateValidator;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo;
+import org.eclipse.milo.opcua.stack.core.util.CertificateUtil;
+import org.eclipse.milo.opcua.stack.core.util.CryptoRestrictions;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS;
+import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME;
+import static org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig.USER_TOKEN_POLICY_X509;
+
+public class Server {
+
+    static {
+        CryptoRestrictions.remove();
+
+        // Required for SecurityPolicy.Aes256_Sha256_RsaPss
+        //Security.addProvider(new BouncyCastleProvider());
+    }
+
+    public static void main(String[] args) throws Exception {
+        Server server = new Server();
+
+        server.startup().get();
+
+        final CompletableFuture<Void> future = new CompletableFuture<>();
+
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> future.complete(null)));
+
+        future.get();
+    }
+
+    private final OpcUaServer server;
+
+    public Server() throws Exception {
+        File securityTempDir = new File(System.getProperty("java.io.tmpdir"), "security");
+        if (!securityTempDir.exists() && !securityTempDir.mkdirs()) {
+            throw new Exception("unable to create security temp dir: " + securityTempDir);
+        }
+        LoggerFactory.getLogger(getClass()).info("security temp dir: {}", securityTempDir.getAbsolutePath());
+
+        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
+
+        DefaultCertificateManager certificateManager = new DefaultCertificateManager(
+                loader.getServerKeyPair(),
+                loader.getServerCertificateChain()
+        );
+
+        File pkiDir = securityTempDir.toPath().resolve("pki").toFile();
+        DirectoryCertificateValidator certificateValidator = new DirectoryCertificateValidator(pkiDir);
+        LoggerFactory.getLogger(getClass()).info("pki dir: {}", pkiDir.getAbsolutePath());
+
+        UsernameIdentityValidator identityValidator = new UsernameIdentityValidator(
+                true,
+                authChallenge -> {
+                    String username = authChallenge.getUsername();
+                    String password = authChallenge.getPassword();
+
+                    boolean userOk = "user".equals(username) && "password1".equals(password);
+                    boolean adminOk = "admin".equals(username) && "password2".equals(password);
+
+                    return userOk || adminOk;
+                }
+        );
+
+        X509IdentityValidator x509IdentityValidator = new X509IdentityValidator(c -> true);
+
+        List<String> bindAddresses = newArrayList();
+        bindAddresses.add("0.0.0.0");
+
+        List<String> endpointAddresses = newArrayList();
+        endpointAddresses.add(HostnameUtil.getHostname());
+        endpointAddresses.addAll(HostnameUtil.getHostnames("0.0.0.0"));
+
+        // The configured application URI must match the one in the certificate(s)
+        String applicationUri = certificateManager.getCertificates().stream()
+                .findFirst()
+                .map(certificate ->
+                        CertificateUtil.getSubjectAltNameField(certificate, CertificateUtil.SUBJECT_ALT_NAME_URI)
+                                .map(Object::toString)
+                                .orElseThrow(() -> new RuntimeException("certificate is missing the application URI")))
+                .orElse("urn:eclipse:milo:examples:server:" + UUID.randomUUID());
+
+        OpcUaServerConfig serverConfig = OpcUaServerConfig.builder()
+                .setApplicationUri(applicationUri)
+                .setApplicationName(LocalizedText.english("Eclipse Milo OPC UA Example Server"))
+                .setBindPort(12686)
+                .setBindAddresses(bindAddresses)
+                .setEndpointAddresses(endpointAddresses)
+                .setBuildInfo(
+                        new BuildInfo(
+                                "urn:eclipse:milo:example-server",
+                                "eclipse",
+                                "eclipse milo example server",
+                                OpcUaServer.SDK_VERSION,
+                                "", DateTime.now()))
+                .setCertificateManager(certificateManager)
+                .setCertificateValidator(certificateValidator)
+                .setIdentityValidator(new CompositeValidator(identityValidator, x509IdentityValidator))
+                .setProductUri("urn:eclipse:milo:example-server")
+                .setServerName("example")
+                .setSecurityPolicies(
+                        EnumSet.of(
+                                SecurityPolicy.None,
+                                SecurityPolicy.Basic128Rsa15,
+                                SecurityPolicy.Basic256,
+                                SecurityPolicy.Basic256Sha256,
+                                SecurityPolicy.Aes128_Sha256_RsaOaep,
+                                SecurityPolicy.Aes256_Sha256_RsaPss))
+                .setUserTokenPolicies(
+                        ImmutableList.of(
+                                USER_TOKEN_POLICY_ANONYMOUS,
+                                USER_TOKEN_POLICY_USERNAME,
+                                USER_TOKEN_POLICY_X509))
+                .build();
+
+        server = new OpcUaServer(serverConfig);
+
+        server.getNamespaceManager().registerAndAdd(
+                MyNamespace.NAMESPACE_URI,
+                idx -> new MyNamespace(server, idx));
+    }
+
+    public OpcUaServer getServer() {
+        return server;
+    }
+
+    public CompletableFuture<OpcUaServer> startup() {
+        return server.startup();
+    }
+
+    public CompletableFuture<OpcUaServer> shutdown() {
+        return server.shutdown();
+    }
+
+}

+ 48 - 0
src/main/java/at/acdp/opcur/opc/SqrtMethod.java

@@ -0,0 +1,48 @@
+package at.acdp.opcur.opc;
+/*
+ * Copyright (c) 2016 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+
+import org.eclipse.milo.opcua.sdk.server.annotations.UaInputArgument;
+import org.eclipse.milo.opcua.sdk.server.annotations.UaMethod;
+import org.eclipse.milo.opcua.sdk.server.annotations.UaOutputArgument;
+import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler.InvocationContext;
+import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler.Out;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SqrtMethod {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @UaMethod
+    public void invoke(
+            InvocationContext context,
+
+            @UaInputArgument(
+                    name = "x",
+                    description = "A value.")
+                    Double x,
+
+            @UaOutputArgument(
+                    name = "x_sqrt",
+                    description = "The positive square root of x. If the argument is NaN or less than zero, the result is NaN.")
+                    Out<Double> xSqrt) {
+
+        System.out.println("sqrt(" + x.toString() + ")");
+        logger.debug("Invoking sqrt() method of Object '{}'", context.getObjectNode().getBrowseName().getName());
+
+        xSqrt.set(Math.sqrt(x));
+    }
+
+}

+ 62 - 0
src/main/java/at/acdp/opcur/opc/ValueLoggingDelegate.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016 Kevin Herron
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *   http://www.eclipse.org/org/documents/edl-v10.html.
+ */
+
+package at.acdp.opcur.opc;
+
+
+import org.eclipse.milo.opcua.sdk.server.api.nodes.VariableNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext;
+import org.eclipse.milo.opcua.sdk.server.nodes.delegates.AttributeDelegate;
+import org.eclipse.milo.opcua.sdk.server.nodes.delegates.DelegatingAttributeDelegate;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ValueLoggingDelegate extends DelegatingAttributeDelegate {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    public ValueLoggingDelegate() {}
+
+    public ValueLoggingDelegate(AttributeDelegate parent) {
+        super(parent);
+    }
+
+    @Override
+    public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
+        DataValue value = super.getValue(context, node);
+
+        // only log external reads
+        if (context.getSession().isPresent()) {
+            logger.info(
+                    "getValue() nodeId={} value={}",
+                    node.getNodeId(), value);
+        }
+
+        return value;
+    }
+
+    @Override
+    public void setValue(AttributeContext context, VariableNode node, DataValue value) throws UaException {
+        // only log external writes
+        if (context.getSession().isPresent()) {
+            logger.info(
+                    "setValue() nodeId={} value={}",
+                    node.getNodeId(), value);
+        }
+
+        super.setValue(context, node, value);
+    }
+
+}

+ 26 - 0
src/main/java/at/acdp/opcur/ur/Activator.java

@@ -0,0 +1,26 @@
+package at.acdp.opcur.ur;
+
+import at.acdp.opcur.opc.Server;
+import com.ur.urcap.api.contribution.installation.swing.SwingInstallationNodeService;
+import com.ur.urcap.api.contribution.program.swing.SwingProgramNodeService;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+	@Override
+	public void start(final BundleContext context) throws Exception {
+		context.registerService(SwingInstallationNodeService.class, new HelloWorldInstallationNodeService(), null);
+		context.registerService(SwingProgramNodeService.class, new HelloWorldProgramNodeService(), null);
+		new Thread(() -> {
+			try {
+				Server.main(null);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}).start();
+	}
+
+	@Override
+	public void stop(BundleContext context) throws Exception {
+	}
+}

+ 78 - 0
src/main/java/at/acdp/opcur/ur/HelloWorldInstallationNodeContribution.java

@@ -0,0 +1,78 @@
+package at.acdp.opcur.ur;
+
+import com.ur.urcap.api.contribution.InstallationNodeContribution;
+import com.ur.urcap.api.contribution.installation.InstallationAPIProvider;
+import com.ur.urcap.api.domain.data.DataModel;
+import com.ur.urcap.api.domain.script.ScriptWriter;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardInputCallback;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardInputFactory;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardTextInput;
+
+public class HelloWorldInstallationNodeContribution implements InstallationNodeContribution {
+
+	private static final String POPUPTITLE_KEY = "popuptitle";
+	private static final String DEFAULT_VALUE = "Hello World Swing";
+	private final HelloWorldInstallationNodeView view;
+	private final KeyboardInputFactory keyboardFactory;
+
+	private DataModel model;
+
+	public HelloWorldInstallationNodeContribution(InstallationAPIProvider apiProvider, DataModel model, HelloWorldInstallationNodeView view) {
+		this.keyboardFactory = apiProvider.getUserInterfaceAPI().getUserInteraction().getKeyboardInputFactory();
+		this.model = model;
+		this.view = view;
+	}
+
+	@Override
+	public void openView() {
+		view.setPopupText(getPopupTitle());
+	}
+
+	@Override
+	public void closeView() {
+
+	}
+
+	public boolean isDefined() {
+		return !getPopupTitle().isEmpty();
+	}
+
+	@Override
+	public void generateScript(ScriptWriter writer) {
+		// Store the popup title in a global variable so it is globally available to all Hello World Swing program nodes.
+		writer.assign("hello_world_swing_popup_title", "\"" + getPopupTitle() + "\"");
+	}
+
+	public String getPopupTitle() {
+		return model.get(POPUPTITLE_KEY, DEFAULT_VALUE);
+	}
+
+	public void setPopupTitle(String message) {
+		if ("".equals(message)) {
+			resetToDefaultValue();
+		} else {
+			model.set(POPUPTITLE_KEY, message);
+		}
+	}
+
+	private void resetToDefaultValue() {
+		view.setPopupText(DEFAULT_VALUE);
+		model.set(POPUPTITLE_KEY, DEFAULT_VALUE);
+	}
+
+	public KeyboardTextInput getInputForTextField() {
+		KeyboardTextInput keyboardInput = keyboardFactory.createStringKeyboardInput();
+		keyboardInput.setInitialValue(getPopupTitle());
+		return keyboardInput;
+	}
+
+	public KeyboardInputCallback<String> getCallbackForTextField() {
+		return new KeyboardInputCallback<String>() {
+			@Override
+			public void onOk(String value) {
+				setPopupTitle(value);
+				view.setPopupText(value);
+			}
+		};
+	}
+}

+ 35 - 0
src/main/java/at/acdp/opcur/ur/HelloWorldInstallationNodeService.java

@@ -0,0 +1,35 @@
+package at.acdp.opcur.ur;
+
+import com.ur.urcap.api.contribution.ViewAPIProvider;
+import com.ur.urcap.api.contribution.installation.ContributionConfiguration;
+import com.ur.urcap.api.contribution.installation.CreationContext;
+import com.ur.urcap.api.contribution.installation.InstallationAPIProvider;
+import com.ur.urcap.api.contribution.installation.swing.SwingInstallationNodeService;
+import com.ur.urcap.api.domain.SystemAPI;
+import com.ur.urcap.api.domain.data.DataModel;
+
+import java.util.Locale;
+
+public class HelloWorldInstallationNodeService implements SwingInstallationNodeService<HelloWorldInstallationNodeContribution, HelloWorldInstallationNodeView> {
+
+	@Override
+	public void configureContribution(ContributionConfiguration configuration) {
+	}
+
+	@Override
+	public String getTitle(Locale locale) {
+		return "Hello World Swing";
+	}
+
+	@Override
+	public HelloWorldInstallationNodeView createView(ViewAPIProvider apiProvider) {
+		SystemAPI systemAPI = apiProvider.getSystemAPI();
+		Style style = systemAPI.getSoftwareVersion().getMajorVersion() >= 5 ? new V5Style() : new V3Style();
+		return new HelloWorldInstallationNodeView(style);
+	}
+
+	@Override
+	public HelloWorldInstallationNodeContribution createInstallationNode(InstallationAPIProvider apiProvider, HelloWorldInstallationNodeView view, DataModel model, CreationContext context) {
+		return new HelloWorldInstallationNodeContribution(apiProvider, model, view);
+	}
+}

+ 89 - 0
src/main/java/at/acdp/opcur/ur/HelloWorldInstallationNodeView.java

@@ -0,0 +1,89 @@
+package at.acdp.opcur.ur;
+
+import com.ur.urcap.api.contribution.installation.swing.SwingInstallationNodeView;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardTextInput;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.JTextPane;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+public class HelloWorldInstallationNodeView implements SwingInstallationNodeView<HelloWorldInstallationNodeContribution> {
+
+	private final Style style;
+	private JTextField jTextField;
+
+	public HelloWorldInstallationNodeView(Style style) {
+		this.style = style;
+	}
+
+	@Override
+	public void buildUI(JPanel jPanel, final HelloWorldInstallationNodeContribution installationNode) {
+		jPanel.setLayout(new BoxLayout(jPanel, BoxLayout.Y_AXIS));
+
+		jPanel.add(createInfo());
+		jPanel.add(createVerticalSpacing());
+		jPanel.add(createInput(installationNode));
+	}
+
+	private Box createInfo() {
+		Box infoBox = Box.createVerticalBox();
+		infoBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+		JTextPane pane = new JTextPane();
+		pane.setBorder(BorderFactory.createEmptyBorder());
+		SimpleAttributeSet attributeSet = new SimpleAttributeSet();
+		StyleConstants.setLineSpacing(attributeSet, 0.5f);
+		StyleConstants.setLeftIndent(attributeSet, 0f);
+		pane.setParagraphAttributes(attributeSet, false);
+		pane.setText("The popup title below is shared between all Hello World program nodes.\nThe title cannot be empty.");
+		pane.setEditable(false);
+		pane.setMaximumSize(pane.getPreferredSize());
+		pane.setBackground(infoBox.getBackground());
+		infoBox.add(pane);
+		return infoBox;
+	}
+
+	private Box createInput(final HelloWorldInstallationNodeContribution installationNode) {
+		Box inputBox = Box.createHorizontalBox();
+		inputBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+		inputBox.add(new JLabel("Popup title:"));
+		inputBox.add(createHorizontalSpacing());
+
+		jTextField = new JTextField();
+		jTextField.setFocusable(false);
+		jTextField.setPreferredSize(style.getInputfieldSize());
+		jTextField.setMaximumSize(jTextField.getPreferredSize());
+		jTextField.addMouseListener(new MouseAdapter() {
+			@Override
+			public void mousePressed(MouseEvent e) {
+				KeyboardTextInput keyboardInput = installationNode.getInputForTextField();
+				keyboardInput.show(jTextField, installationNode.getCallbackForTextField());
+			}
+		});
+		inputBox.add(jTextField);
+
+		return inputBox;
+	}
+
+	private Component createHorizontalSpacing() {
+		return Box.createRigidArea(new Dimension(style.getHorizontalSpacing(), 0));
+	}
+
+	private Component createVerticalSpacing() {
+		return Box.createRigidArea(new Dimension(0, style.getVerticalSpacing()));
+	}
+
+	public void setPopupText(String t) {
+		jTextField.setText(t);
+	}
+}

+ 108 - 0
src/main/java/at/acdp/opcur/ur/HelloWorldProgramNodeContribution.java

@@ -0,0 +1,108 @@
+package at.acdp.opcur.ur;
+
+import com.ur.urcap.api.contribution.ProgramNodeContribution;
+import com.ur.urcap.api.contribution.program.ProgramAPIProvider;
+import com.ur.urcap.api.domain.ProgramAPI;
+import com.ur.urcap.api.domain.data.DataModel;
+import com.ur.urcap.api.domain.script.ScriptWriter;
+import com.ur.urcap.api.domain.undoredo.UndoRedoManager;
+import com.ur.urcap.api.domain.undoredo.UndoableChanges;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardInputCallback;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardInputFactory;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardTextInput;
+
+public class HelloWorldProgramNodeContribution implements ProgramNodeContribution {
+	private static final String NAME = "name";
+
+	private final ProgramAPI programAPI;
+	private final UndoRedoManager undoRedoManager;
+	private final KeyboardInputFactory keyboardFactory;
+
+	private final HelloWorldProgramNodeView view;
+	private final DataModel model;
+
+	public HelloWorldProgramNodeContribution(ProgramAPIProvider apiProvider, HelloWorldProgramNodeView view, DataModel model) {
+		this.programAPI = apiProvider.getProgramAPI();
+		this.undoRedoManager = apiProvider.getProgramAPI().getUndoRedoManager();
+		this.keyboardFactory = apiProvider.getUserInterfaceAPI().getUserInteraction().getKeyboardInputFactory();
+
+		this.view = view;
+		this.model = model;
+	}
+
+	@Override
+	public void openView() {
+		view.setPopupText(getName());
+		updatePopupMessageAndPreview();
+	}
+
+	@Override
+	public void closeView() {
+	}
+
+	@Override
+	public String getTitle() {
+		return "Hello World Swing: " + (model.isSet(NAME) ? getName() : "");
+	}
+
+	@Override
+	public boolean isDefined() {
+		return getInstallation().isDefined() && !getName().isEmpty();
+	}
+
+	@Override
+	public void generateScript(ScriptWriter writer) {
+		// Directly generate this Program Node's popup message + access the popup title through a global variable
+		writer.appendLine("popup(\"" + generatePopupMessage() + "\", hello_world_swing_popup_title, False, False, blocking=True)");
+		writer.writeChildren();
+	}
+
+	public KeyboardTextInput getKeyboardForTextField() {
+		KeyboardTextInput keyboardInput = keyboardFactory.createStringKeyboardInput();
+		keyboardInput.setInitialValue(getName());
+		return keyboardInput;
+	}
+
+	public KeyboardInputCallback<String> getCallbackForTextField() {
+		return new KeyboardInputCallback<String>() {
+			@Override
+			public void onOk(String value) {
+				setPopupTitle(value);
+				view.setPopupText(value);
+			}
+		};
+	}
+
+	public void setPopupTitle(final String value) {
+		undoRedoManager.recordChanges(new UndoableChanges() {
+			@Override
+			public void executeChanges() {
+				if ("".equals(value)) {
+					model.remove(NAME);
+				} else {
+					model.set(NAME, value);
+				}
+			}
+		});
+
+		updatePopupMessageAndPreview();
+	}
+
+	private String generatePopupMessage() {
+		return model.isSet(NAME) ? "Hello " + getName() + ", welcome to PolyScope!" : "No name set";
+	}
+
+	private void updatePopupMessageAndPreview() {
+		view.setMessagePreview(generatePopupMessage());
+		view.setTitlePreview(getInstallation().isDefined() ? getInstallation().getPopupTitle() : "No title set");
+	}
+
+	private String getName() {
+		return model.get(NAME, "");
+	}
+
+	private HelloWorldInstallationNodeContribution getInstallation() {
+		return programAPI.getInstallationNode(HelloWorldInstallationNodeContribution.class);
+	}
+
+}

+ 51 - 0
src/main/java/at/acdp/opcur/ur/HelloWorldProgramNodeService.java

@@ -0,0 +1,51 @@
+package at.acdp.opcur.ur;
+
+import com.ur.urcap.api.contribution.ViewAPIProvider;
+import com.ur.urcap.api.contribution.program.ContributionConfiguration;
+import com.ur.urcap.api.contribution.program.CreationContext;
+import com.ur.urcap.api.contribution.program.ProgramAPIProvider;
+import com.ur.urcap.api.contribution.program.swing.SwingProgramNodeService;
+import com.ur.urcap.api.domain.SystemAPI;
+import com.ur.urcap.api.domain.data.DataModel;
+
+import java.util.Locale;
+
+public class HelloWorldProgramNodeService implements SwingProgramNodeService<HelloWorldProgramNodeContribution, HelloWorldProgramNodeView> {
+
+	@Override
+	public String getId() {
+		return "HelloWorldSwingNode";
+	}
+
+	@Override
+	public void configureContribution(ContributionConfiguration configuration) {
+		configuration.setChildrenAllowed(true);
+	}
+
+	@Override
+	public String getTitle(Locale locale) {
+		String title = "Hello World Swing";
+		if ("ru".equals(locale.getLanguage())) {
+			title = "Привет мир Swing";
+		} else if ("de".equals(locale.getLanguage())) {
+			title = "Hallo Welt Swing";
+		}
+		return title;
+	}
+
+	@Override
+	public HelloWorldProgramNodeView createView(ViewAPIProvider apiProvider) {
+		SystemAPI systemAPI = apiProvider.getSystemAPI();
+		Style style = systemAPI.getSoftwareVersion().getMajorVersion() >= 5 ? new V5Style() : new V3Style();
+		return new HelloWorldProgramNodeView(style);
+	}
+
+	@Override
+	public HelloWorldProgramNodeContribution createNode(
+			ProgramAPIProvider apiProvider,
+			HelloWorldProgramNodeView view,
+			DataModel model,
+			CreationContext context) {
+		return new HelloWorldProgramNodeContribution(apiProvider, view, model);
+	}
+}

+ 116 - 0
src/main/java/at/acdp/opcur/ur/HelloWorldProgramNodeView.java

@@ -0,0 +1,116 @@
+package at.acdp.opcur.ur;
+
+import com.ur.urcap.api.contribution.ContributionProvider;
+import com.ur.urcap.api.contribution.program.swing.SwingProgramNodeView;
+import com.ur.urcap.api.domain.userinteraction.keyboard.KeyboardTextInput;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+public class HelloWorldProgramNodeView implements SwingProgramNodeView<HelloWorldProgramNodeContribution>{
+
+	private final Style style;
+	private JTextField jTextField;
+	private JLabel previewTitle;
+	private JLabel previewMessage;
+
+	public HelloWorldProgramNodeView(Style style) {
+		this.style = style;
+	}
+
+	@Override
+	public void buildUI(JPanel jPanel, final ContributionProvider<HelloWorldProgramNodeContribution> provider) {
+		jPanel.setLayout(new BoxLayout(jPanel, BoxLayout.Y_AXIS));
+
+		jPanel.add(createInfo());
+		jPanel.add(createVerticalSpacing(style.getVerticalSpacing()));
+		jPanel.add(createInput(provider));
+		jPanel.add(createVerticalSpacing(style.getExtraLargeVerticalSpacing()));
+		jPanel.add(createPreview());
+	}
+
+	private Box createInfo() {
+		Box infoBox = Box.createHorizontalBox();
+		infoBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+		infoBox.add(new JLabel("This program node will open a popup on execution."));
+		return infoBox;
+	}
+
+	private Box createInput(final ContributionProvider<HelloWorldProgramNodeContribution> provider) {
+		Box inputBox = Box.createHorizontalBox();
+		inputBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+		inputBox.add(new JLabel("Enter your name:"));
+		inputBox.add(createHorizontalSpacing());
+
+		jTextField = new JTextField();
+		jTextField.setFocusable(false);
+		jTextField.setPreferredSize(style.getInputfieldSize());
+		jTextField.setMaximumSize(jTextField.getPreferredSize());
+		jTextField.addMouseListener(new MouseAdapter() {
+			@Override
+			public void mousePressed(MouseEvent e) {
+				KeyboardTextInput keyboardInput = provider.get().getKeyboardForTextField();
+				keyboardInput.show(jTextField, provider.get().getCallbackForTextField());
+			}
+		});
+
+		inputBox.add(jTextField);
+		return inputBox;
+	}
+
+	private Box createPreview() {
+		Box previewBox = Box.createVerticalBox();
+		JLabel preview = new JLabel("Preview");
+		preview.setFont(preview.getFont().deriveFont(Font.BOLD, style.getSmallHeaderFontSize()));
+
+		Box titleBox = Box.createHorizontalBox();
+		titleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+		titleBox.add(new JLabel("Title:"));
+		titleBox.add(createHorizontalSpacing());
+		previewTitle = new JLabel("my title");
+		titleBox.add(previewTitle);
+
+		Box messageBox = Box.createHorizontalBox();
+		messageBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+		messageBox.add(new JLabel("Message:"));
+		messageBox.add(createHorizontalSpacing());
+		previewMessage = new JLabel("my message");
+		messageBox.add(previewMessage);
+
+		previewBox.add(preview);
+		previewBox.add(createVerticalSpacing(style.getLargeVerticalSpacing()));
+		previewBox.add(titleBox);
+		previewBox.add(createVerticalSpacing(style.getVerticalSpacing()));
+		previewBox.add(messageBox);
+
+		return previewBox;
+	}
+
+	private Component createVerticalSpacing(int height) {
+		return Box.createRigidArea(new Dimension(0, height));
+	}
+
+	private Component createHorizontalSpacing() {
+		return Box.createRigidArea(new Dimension(style.getHorizontalSpacing(), 0));
+	}
+
+	public void setPopupText(String popupText) {
+		jTextField.setText(popupText);
+	}
+
+	public void setMessagePreview(String message) {
+		previewMessage.setText(message);
+	}
+
+	public void setTitlePreview(String title) {
+		previewTitle.setText(title);
+	}
+}

+ 33 - 0
src/main/java/at/acdp/opcur/ur/Style.java

@@ -0,0 +1,33 @@
+package at.acdp.opcur.ur;
+
+import java.awt.*;
+
+public abstract class Style {
+	private static final int HORIZONTAL_SPACING = 10;
+	private static final int VERTICAL_SPACING = 10;
+	private static final int LARGE_VERTICAL_SPACING = 25;
+    private static final int XLARGE_VERTICAL_SPACING = 50;
+	private static final int SMALL_HEADER_FONT_SIZE = 16;
+
+	public int getHorizontalSpacing() {
+		return HORIZONTAL_SPACING;
+	}
+
+	public int getVerticalSpacing() {
+		return VERTICAL_SPACING;
+	}
+
+    public int getExtraLargeVerticalSpacing() {
+        return XLARGE_VERTICAL_SPACING;
+    }
+
+	public int getLargeVerticalSpacing() {
+		return LARGE_VERTICAL_SPACING;
+	}
+
+	public int getSmallHeaderFontSize() {
+		return SMALL_HEADER_FONT_SIZE;
+	}
+
+	public abstract Dimension getInputfieldSize();
+}

+ 12 - 0
src/main/java/at/acdp/opcur/ur/V3Style.java

@@ -0,0 +1,12 @@
+package at.acdp.opcur.ur;
+
+import java.awt.*;
+
+public class V3Style extends Style {
+	private static final Dimension INPUTFIELD_SIZE = new Dimension(200, 24);
+
+	@Override
+	public Dimension getInputfieldSize() {
+		return INPUTFIELD_SIZE;
+	}
+}

+ 12 - 0
src/main/java/at/acdp/opcur/ur/V5Style.java

@@ -0,0 +1,12 @@
+package at.acdp.opcur.ur;
+
+import java.awt.*;
+
+public class V5Style extends Style {
+	private static final Dimension INPUTFIELD_SIZE = new Dimension(200, 30);
+
+	@Override
+	public Dimension getInputfieldSize() {
+		return INPUTFIELD_SIZE;
+	}
+}