Explorar o código

完善iec和opc

hfxc226 %!s(int64=2) %!d(string=hai) anos
pai
achega
287c6bdbf4
Modificáronse 82 ficheiros con 6792 adicións e 59 borrados
  1. 9 7
      platform-iec/src/main/java/com/ydl/iec/iec104/core/ScheduledTaskPool.java
  2. 9 2
      platform-opc/pom.xml
  3. 1 1
      platform-opc/src/main/java/com/platform/opc/OpcApplication.java
  4. 103 49
      platform-opc/src/main/java/com/platform/opc/util/OpcUAClientUtil.java
  5. 25 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/Categories.java
  6. 30 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/Constants.java
  7. 25 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/EventHandler.java
  8. 148 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/FILETIME.java
  9. 35 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/KeyedResult.java
  10. 28 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/KeyedResultSet.java
  11. 48 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/Result.java
  12. 29 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/ResultSet.java
  13. 33 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/BaseCOMObject.java
  14. 114 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/EnumGUID.java
  15. 113 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/EnumString.java
  16. 50 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/EventHandlerImpl.java
  17. 58 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/Helper.java
  18. 83 0
      platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/OPCCommon.java
  19. 36 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/Constants.java
  20. 27 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/IOPCDataCallback.java
  21. 42 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/IORequest.java
  22. 44 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCBROWSEDIRECTION.java
  23. 44 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCBROWSETYPE.java
  24. 41 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCDATASOURCE.java
  25. 53 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCENUMSCOPE.java
  26. 96 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCGroupState.java
  27. 100 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCITEMDEF.java
  28. 87 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCITEMRESULT.java
  29. 95 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCITEMSTATE.java
  30. 41 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCNAMESPACETYPE.java
  31. 53 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCSERVERSTATE.java
  32. 170 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCSERVERSTATUS.java
  33. 46 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/PropertyDescription.java
  34. 50 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/ValueData.java
  35. 62 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/WriteRequest.java
  36. 116 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCAsyncIO2.java
  37. 29 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCBrowse.java
  38. 142 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCBrowseServerAddressSpace.java
  39. 214 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCDataCallback.java
  40. 178 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCGroupStateMgt.java
  41. 56 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCItemIO.java
  42. 180 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCItemMgt.java
  43. 130 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCItemProperties.java
  44. 161 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCServer.java
  45. 92 0
      platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCSyncIO.java
  46. 52 0
      platform-opc/src/main/java/org/openscada/opc/dcom/list/ClassDetails.java
  47. 20 0
      platform-opc/src/main/java/org/openscada/opc/dcom/list/Constants.java
  48. 137 0
      platform-opc/src/main/java/org/openscada/opc/dcom/list/impl/OPCServerList.java
  49. 20 0
      platform-opc/src/main/java/org/openscada/opc/lib/common/AlreadyConnectedException.java
  50. 131 0
      platform-opc/src/main/java/org/openscada/opc/lib/common/ConnectionInformation.java
  51. 20 0
      platform-opc/src/main/java/org/openscada/opc/lib/common/NotConnectedException.java
  52. 294 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/AccessBase.java
  53. 20 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/AccessStateListener.java
  54. 53 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/AddFailedException.java
  55. 110 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/Async20Access.java
  56. 183 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/AutoReconnectController.java
  57. 18 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/AutoReconnectListener.java
  58. 44 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/AutoReconnectState.java
  59. 18 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/DataCallback.java
  60. 23 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/DuplicateGroupException.java
  61. 66 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/ErrorMessageResolver.java
  62. 362 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/Group.java
  63. 67 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/Item.java
  64. 127 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/ItemState.java
  65. 432 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/Server.java
  66. 18 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/ServerConnectionStateListener.java
  67. 20 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/ServerStateListener.java
  68. 103 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/ServerStateOperation.java
  69. 91 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/ServerStateReader.java
  70. 95 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/SyncAccess.java
  71. 37 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/UnknownGroupException.java
  72. 36 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/WriteRequest.java
  73. 29 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/browser/Access.java
  74. 112 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/browser/BaseBrowser.java
  75. 108 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/browser/Branch.java
  76. 66 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/browser/FlatBrowser.java
  77. 54 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/browser/Leaf.java
  78. 225 0
      platform-opc/src/main/java/org/openscada/opc/lib/da/browser/TreeBrowser.java
  79. 36 0
      platform-opc/src/main/java/org/openscada/opc/lib/list/Categories.java
  80. 58 0
      platform-opc/src/main/java/org/openscada/opc/lib/list/Category.java
  81. 175 0
      platform-opc/src/main/java/org/openscada/opc/lib/list/ServerList.java
  82. 6 0
      platform-opc/src/main/resources/opc.properties

+ 9 - 7
platform-iec/src/main/java/com/ydl/iec/iec104/core/ScheduledTaskPool.java

@@ -188,12 +188,11 @@ public class ScheduledTaskPool {
                             Thread.sleep(1000 * 60 * 2);// 2分钟判断一次是否发送一次电度总召唤,确保一个小时必须发一次
                             // 判断是否需要发送电度
                             if (isNeedSendDegree()) {
-                                if ( sendDegreeCallNum==0) {
+                                if (sendDegreeCallNum == 0) {
                                     LOGGER.info("电度召唤指令,并累加" + sendDegreeCallNum++);
                                     ctx.channel().writeAndFlush(BasicInstruction104.getGeneralCallRuleDetail104Degree());
-                                }else{
-                                    LOGGER.info("电度召唤指令该小时内,已经发送,开始清零" );
-                                    sendDegreeCallNum = 0;
+                                } else {
+                                    LOGGER.info("电度召唤指令该小时内,已经发送,不再发送");
                                 }
                             }
                         } catch (Exception e) {
@@ -213,20 +212,23 @@ public class ScheduledTaskPool {
      *
      * @return
      */
-    private static boolean isNeedSendDegree() {
+    private boolean isNeedSendDegree() {
         LocalTime time = LocalTime.now();
         int minute = time.getMinute();
         System.out.println(minute);
-        if (minute >= 3 && minute < 5) {
+        if (minute >= 3 && minute < 10) {
             return true;
         }
+        if (minute >= 10) {
+            sendDegreeCallNum = 0;
+        }
         return false;
     }
 
     public static void main(String[] args) {
         // MessageDetail messageDetail = BasicInstruction104.getGeneralCallRuleDetail104();
         // System.out.println(JSON.toJSONString(messageDetail).getBytes());
-        System.out.println(isNeedSendDegree());
+        // System.out.println(ScheduledTaskPool.isNeedSendDegree());
     }
 
     /**

+ 9 - 2
platform-opc/pom.xml

@@ -94,6 +94,13 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.jinterop</groupId>
+            <artifactId>j-interop</artifactId>
+            <version>3.0.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/src/lib/j-interop-3.0.0.jar</systemPath>
+        </dependency>
         <dependency>
             <groupId>org.openscada.jinterop</groupId>
             <artifactId>org.openscada.jinterop.core</artifactId>
@@ -110,7 +117,7 @@
                 </exclusion>
             </exclusions>
         </dependency>
-        <dependency>
+        <!--<dependency>
             <groupId>org.openscada.utgard</groupId>
             <artifactId>org.openscada.opc.dcom</artifactId>
             <version>1.5.0</version>
@@ -119,7 +126,7 @@
             <groupId>org.openscada.utgard</groupId>
             <artifactId>org.openscada.opc.lib</artifactId>
             <version>1.5.0</version>
-        </dependency>
+        </dependency>-->
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>

+ 1 - 1
platform-opc/src/main/java/com/platform/opc/OpcApplication.java

@@ -30,6 +30,6 @@ public class OpcApplication implements CommandLineRunner {
     @Override
     public void run(String... args) throws Exception {
         OpcUAClientUtil.run();
-        Thread.sleep(1000000);
+        Thread.sleep(1000);
     }
 }

+ 103 - 49
platform-opc/src/main/java/com/platform/opc/util/OpcUAClientUtil.java

@@ -1,19 +1,23 @@
 package com.platform.opc.util;
 
 import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIErrorCodes;
+import org.jinterop.dcom.common.JISystem;
+import org.openscada.opc.dcom.list.ClassDetails;
 import org.openscada.opc.lib.common.ConnectionInformation;
 
 import org.jinterop.dcom.common.JIException;
 import org.jinterop.dcom.core.JIString;
 import org.jinterop.dcom.core.JIVariant;
 import org.openscada.opc.lib.common.ConnectionInformation;
-import org.openscada.opc.lib.da.AccessBase;
-import org.openscada.opc.lib.da.DataCallback;
-import org.openscada.opc.lib.da.Item;
-import org.openscada.opc.lib.da.ItemState;
-import org.openscada.opc.lib.da.Server;
-import org.openscada.opc.lib.da.SyncAccess;
+import org.openscada.opc.lib.common.NotConnectedException;
+import org.openscada.opc.lib.da.*;
+import org.openscada.opc.lib.list.Categories;
+import org.openscada.opc.lib.list.Category;
+import org.openscada.opc.lib.list.ServerList;
 
+import java.net.UnknownHostException;
+import java.util.Collection;
 import java.util.concurrent.Executors;
 
 @Slf4j
@@ -21,66 +25,59 @@ public class OpcUAClientUtil {
 
     public static void run() throws Exception {
         // 连接信息
+        JISystem.setAutoRegisteration(true);
         final ConnectionInformation ci = new ConnectionInformation();
         ci.setHost("192.168.108.108");         // 电脑IP
-        ci.setDomain("");                  // 域,为空就行
-        ci.setUser("OPCUser");             // 电脑上自己建好的用户名
-        ci.setPassword("123456");          // 用户名的密码
+        // ci.setDomain("");                  // 域,为空就行
+        ci.setUser("Administrator");             // 电脑上自己建好的用户名
+        ci.setPassword("Hollysys");          // 用户名的密码
 
         // 使用MatrikonOPC Server的配置
-        // ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305"); // MatrikonOPC的注册表ID,可以在“组件服务”里看到
-        // final String itemId = "u.u";    // 项的名字按实际
+        // ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // MatrikonOPC的注册表ID,可以在“组件服务”里看到
+        //ci.setProgId("Matrikon.OPC.Simulation.1");
+
+        // 和利时
+        ci.setClsid("001AAAA6-FB54-4627-84B2-8777379E5868");
+        ci.setProgId("Hollysys.HOLLiASiComm.1");
 
         // 使用KEPServer的配置
-        ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // KEPServer的注册表ID,可以在“组件服务”里看到
-        final String itemId = "u.u.u";    // 项的名字按实际,没有实际PLC,用的模拟器:simulator
+        // ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // KEPServer的注册表ID,可以在“组件服务”里看到
+        // ci.setProgId("Kepware.KEPServerEX.V6");
+        // final String itemId = "u.u.u";    // 项的名字按实际,没有实际PLC,用的模拟器:simulator
         // final String itemId = "通道 1.设备 1.标记 1";
 
         // 启动服务
         final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
-
+        //AutoReconnectController autos = new AutoReconnectController(server);
         try {
-            // 连接到服务
+            //建立连接
+            System.out.println("Start开始连接----");
             server.connect();
-            // add sync access, poll every 500 ms,启动一个同步的access用来读取地址上的值,线程池每500ms读值一次
-            // 这个是用来循环读值的,只读一次值不用这样
-            final AccessBase access = new SyncAccess(server, 500);
-            // 这是个回调函数,就是读到值后执行这个打印,是用匿名类写的,当然也可以写到外面去
+            System.out.println("Success连接成功----");
+            ServerList serverList = new ServerList("192.168.108.108", "Administrator", "Hollysys", "");
+            Collection<ClassDetails> classDetails = serverList
+                    .listServersWithDetails(new Category[]{
+                            Categories.OPCDAServer10}, new Category[]{});
+            for (ClassDetails cds : classDetails) {
+                System.out.println(cds.getProgId() + "=" + cds.getDescription());
+            }
+            Collection<String> items = server.getFlatBrowser().browse();
+            for (String item : items) {
+                System.out.println("item=" + item);
+            }
+            final String itemId = "Channel1.Device1.AT_11001_AV";
+            System.out.println("连接成功");
+            final AccessBase access = new SyncAccess(server, 1000);
+            final int[] i = {1};
             access.addItem(itemId, new DataCallback() {
                 @Override
-                public void changed(Item item, ItemState itemState) {
-                    int type = 0;
+                public void changed(Item item, ItemState state) {
+                    // also dump value
                     try {
-                        type = itemState.getValue().getType(); // 类型实际是数字,用常量定义的
+                        System.out.println(i[0]++ + ":" + getVal(state.getValue()));
                     } catch (JIException e) {
                         e.printStackTrace();
                     }
-                    System.out.println("监控项的数据类型是:-----" + type);
-                    System.out.println("监控项的时间戳是:-----" + itemState.getTimestamp().getTime());
-                    System.out.println("监控项的详细信息是:-----" + itemState);
-
-                    // 如果读到是short类型的值
-                    if (type == JIVariant.VT_I2) {
-                        short n = 0;
-                        try {
-                            n = itemState.getValue().getObjectAsShort();
-                        } catch (JIException e) {
-                            e.printStackTrace();
-                        }
-                        System.out.println("-----short类型值: " + n);
-                    }
-
-                    // 如果读到是字符串类型的值
-                    if(type == JIVariant.VT_BSTR) {  // 字符串的类型是8
-                        JIString value = null;
-                        try {
-                            value = itemState.getValue().getObjectAsString();
-                        } catch (JIException e) {
-                            e.printStackTrace();
-                        } // 按字符串读取
-                        String str = value.getString(); // 得到字符串
-                        System.out.println("-----String类型值: " + str);
-                    }
                 }
             });
             // start reading,开始读值
@@ -90,7 +87,64 @@ public class OpcUAClientUtil {
             // stop reading,停止读取
             access.unbind();
         } catch (final JIException e) {
-            System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));
+            e.printStackTrace();
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+        } finally {
+            server.disconnect();
         }
+
+    }
+
+    /**
+     * 根据类型读取数据
+     *
+     * @param var
+     * @return
+     * @throws JIException
+     */
+    private static Object getVal(JIVariant var) throws JIException {
+        Object value;
+        int type = var.getType();
+        switch (type) {
+            case JIVariant.VT_I2:
+                value = var.getObjectAsShort();
+                break;
+            case JIVariant.VT_I4:
+                value = var.getObjectAsInt();
+                break;
+            case JIVariant.VT_I8:
+                value = var.getObjectAsLong();
+                break;
+            case JIVariant.VT_R4:
+                value = var.getObjectAsFloat();
+                break;
+            case JIVariant.VT_R8:
+                value = var.getObjectAsDouble();
+                break;
+            case JIVariant.VT_BSTR:
+                value = var.getObjectAsString2();
+                break;
+            case JIVariant.VT_BOOL:
+                value = var.getObjectAsBoolean();
+                break;
+            case JIVariant.VT_UI2:
+            case JIVariant.VT_UI4:
+                value = var.getObjectAsUnsigned().getValue();
+                break;
+            case JIVariant.VT_EMPTY:
+                throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is Empty.");
+            case JIVariant.VT_NULL:
+                throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is null.");
+            default:
+                throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Unknown Type.");
+        }
+        return value;
+    }
+
+    public static void main(String[] args) throws Exception {
+        run();
     }
 }

+ 25 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/Categories.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+public interface Categories {
+    public static final String OPCDAServer10 = "63D5F430-CFE4-11d1-B2C8-0060083BA1FB";
+
+    public static final String OPCDAServer20 = "63D5F432-CFE4-11d1-B2C8-0060083BA1FB";
+
+    public static final String OPCDAServer30 = "CC603642-66D7-48f1-B69A-B625E73652D7";
+
+    public static final String XMLDAServer10 = "3098EDA4-A006-48b2-A27F-247453959408";
+
+}

+ 30 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/Constants.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+public interface Constants {
+    public static final String IConnectionPointContainer_IID = "B196B284-BAB4-101A-B69C-00AA00341D07";
+
+    public static final String IConnectionPoint_IID = "B196B286-BAB4-101A-B69C-00AA00341D07";
+
+    public static final String IOPCCommon_IID = "F31DFDE2-07B6-11D2-B2D8-0060083BA1FB";
+
+    public static final String IEnumString_IID = "00000101-0000-0000-C000-000000000046";
+
+    public static final String IEnumGUID_IID = "0002E000-0000-0000-C000-000000000046";
+
+    public static final int S_OK = 0;
+
+    public static final int S_FALSE = 1;
+}

+ 25 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/EventHandler.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.IJIComObject;
+
+public interface EventHandler {
+    public String getIdentifier();
+
+    public IJIComObject getObject();
+
+    public void detach() throws JIException;
+}

+ 148 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/FILETIME.java

@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIStruct;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+public class FILETIME {
+    private int high = 0;
+
+    private int low = 0;
+
+    public FILETIME() {
+    }
+
+    public FILETIME(final FILETIME arg0) {
+        this.high = arg0.high;
+        this.low = arg0.low;
+    }
+
+    public FILETIME(final int high, final int low) {
+        this.high = high;
+        this.low = low;
+    }
+
+    public int getHigh() {
+        return this.high;
+    }
+
+    public void setHigh(final int high) {
+        this.high = high;
+    }
+
+    public int getLow() {
+        return this.low;
+    }
+
+    public void setLow(final int low) {
+        this.low = low;
+    }
+
+    @Override
+    public int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + this.high;
+        result = PRIME * result + this.low;
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final FILETIME other = (FILETIME) obj;
+        if (this.high != other.high) {
+            return false;
+        }
+        if (this.low != other.low) {
+            return false;
+        }
+        return true;
+    }
+
+    public static JIStruct getStruct() throws JIException {
+        final JIStruct struct = new JIStruct();
+
+        struct.addMember(Integer.class);
+        struct.addMember(Integer.class);
+
+        return struct;
+    }
+
+    public static FILETIME fromStruct(final JIStruct struct) {
+        final FILETIME ft = new FILETIME();
+
+        ft.setLow((Integer) struct.getMember(0));
+        ft.setHigh((Integer) struct.getMember(1));
+
+        return ft;
+    }
+
+    public Calendar asCalendar() {
+        final Calendar c = Calendar.getInstance();
+
+        /*
+         * The following "strange" stuff is needed since we miss a ulong type
+         */
+        long i = 0xFFFFFFFFL & this.high;
+        i = i << 32;
+        long j = 0xFFFFFFFFFFFFFFFFL & i;
+
+        i = 0xFFFFFFFFL & this.low;
+        j += i;
+        j /= 10000L;
+        j -= 11644473600000L;
+
+        c.setTimeInMillis(j);
+
+        return c;
+    }
+
+    public Calendar asBigDecimalCalendar() {
+        final Calendar c = Calendar.getInstance();
+
+        /*
+         * The following "strange" stuff is needed since we miss a ulong type
+         */
+        long i = 0xFFFFFFFFL & this.high;
+        i = i << 32;
+        BigDecimal d1 = new BigDecimal(0xFFFFFFFFFFFFFFFFL & i);
+
+        i = 0xFFFFFFFFL & this.low;
+        d1 = d1.add(new BigDecimal(i));
+        d1 = d1.divide(new BigDecimal(10000L));
+        d1 = d1.subtract(new BigDecimal(11644473600000L));
+
+        c.setTimeInMillis(d1.longValue());
+
+        return c;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s/%s", this.high, this.low);
+    }
+}

+ 35 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/KeyedResult.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+public class KeyedResult<K, V> extends Result<V> {
+    private K key;
+
+    public KeyedResult() {
+        super();
+    }
+
+    public KeyedResult(final K key, final V value, final int errorCode) {
+        super(value, errorCode);
+        this.key = key;
+    }
+
+    public K getKey() {
+        return this.key;
+    }
+
+    public void setKey(final K key) {
+        this.key = key;
+    }
+}

+ 28 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/KeyedResultSet.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+import java.util.ArrayList;
+
+public class KeyedResultSet<K, V> extends ArrayList<KeyedResult<K, V>> {
+    private static final long serialVersionUID = 1L;
+
+    public KeyedResultSet() {
+        super();
+    }
+
+    public KeyedResultSet(final int size) {
+        super(size); // me
+    }
+}

+ 48 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/Result.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+public class Result<T> {
+    private T value;
+
+    private int errorCode;
+
+    public Result() {
+    }
+
+    public Result(final T value, final int errorCode) {
+        this.value = value;
+        this.errorCode = errorCode;
+    }
+
+    public int getErrorCode() {
+        return this.errorCode;
+    }
+
+    public void setErrorCode(final int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public T getValue() {
+        return this.value;
+    }
+
+    public void setValue(final T value) {
+        this.value = value;
+    }
+
+    public boolean isFailed() {
+        return this.errorCode != 0;
+    }
+}

+ 29 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/ResultSet.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common;
+
+import java.util.ArrayList;
+
+public class ResultSet<T> extends ArrayList<Result<T>> {
+
+    private static final long serialVersionUID = 6392417310208978252L;
+
+    public ResultSet() {
+        super();
+    }
+
+    public ResultSet(final int size) {
+        super(size); // me
+    }
+}

+ 33 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/BaseCOMObject.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common.impl;
+
+import org.jinterop.dcom.core.IJIComObject;
+
+public class BaseCOMObject {
+    private IJIComObject comObject = null;
+
+    /**
+     * Create a new base COM object
+     *
+     * @param comObject The COM object to wrap but be addRef'ed
+     */
+    public BaseCOMObject(final IJIComObject comObject) {
+        this.comObject = comObject;
+    }
+
+    protected synchronized IJIComObject getCOMObject() {
+        return this.comObject;
+    }
+}

+ 114 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/EnumGUID.java

@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.IJIComObject;
+import org.jinterop.dcom.core.JIArray;
+import org.jinterop.dcom.core.JICallBuilder;
+import org.jinterop.dcom.core.JIFlags;
+import rpc.core.UUID;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class EnumGUID extends BaseCOMObject {
+    public static final int DEFAULT_BATCH_SIZE = Integer.getInteger("openscada.dcom.enum-batch-size", 10);
+
+    public EnumGUID(final IJIComObject enumStringObject) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(enumStringObject.queryInterface(org.openscada.opc.dcom.common.Constants.IEnumGUID_IID));
+    }
+
+    public int next(final List<UUID> list, final int num) throws JIException {
+        if (num <= 0) {
+            return 0;
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsInt(num, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(num, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIArray(UUID.class, null, 1, true, true), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+
+        Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        UUID[] resultData = (UUID[]) ((JIArray) result[0]).getArrayInstance();
+        Integer cnt = (Integer) result[1];
+
+        for (int i = 0; i < cnt; i++) {
+            list.add(resultData[i]);
+        }
+        return cnt;
+    }
+
+    public Collection<UUID> next(final int num) throws JIException {
+        List<UUID> list = new ArrayList<UUID>(num);
+        next(list, num);
+        return list;
+    }
+
+    public void skip(final int num) throws JIException {
+        if (num <= 0) {
+            return;
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsInt(num, JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+
+    public void reset() throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        getCOMObject().call(callObject);
+    }
+
+    public EnumGUID cloneObject() throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        IJIComObject object = (IJIComObject) result[0];
+
+        return new EnumGUID(object);
+    }
+
+    public Collection<UUID> asCollection(final int batchSize) throws JIException {
+        reset();
+
+        List<UUID> data = new ArrayList<UUID>();
+        int i = 0;
+        do {
+            i = next(data, batchSize);
+        } while (i == batchSize);
+
+        return data;
+    }
+
+    public Collection<UUID> asCollection() throws JIException {
+        return asCollection(DEFAULT_BATCH_SIZE);
+    }
+
+}

+ 113 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/EnumString.java

@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class EnumString extends BaseCOMObject {
+    public static final int DEFAULT_BATCH_SIZE = Integer.getInteger("openscada.dcom.enum-batch-size", 10);
+
+    public EnumString(final IJIComObject enumStringObject) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(enumStringObject.queryInterface(org.openscada.opc.dcom.common.Constants.IEnumString_IID));
+    }
+
+    public int next(final List<String> list, final int num) throws JIException {
+        if (num <= 0) {
+            return 0;
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsInt(num, JIFlags.FLAG_NULL);
+        //callObject.addInParamAsInt ( num, JIFlags.FLAG_NULL );
+        //callObject.addOutParamAsObject ( new JIArray ( new JIPointer ( new JIString (
+        //        JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR ) ), null, 1, true, true ), JIFlags.FLAG_NULL );
+        callObject.addOutParamAsObject(new JIArray(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR), null, 1, true, true), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+
+        Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        //JIPointer[] resultData = (JIPointer[]) ( (JIArray) ( result[0] ) ).getArrayInstance ();
+        JIString[] resultData = (JIString[]) ((JIArray) result[0]).getArrayInstance();
+        Integer cnt = (Integer) result[1];
+
+        for (int i = 0; i < cnt; i++) {
+            //list.add ( ( (JIString)resultData[i].getReferent () ).getString () );
+            list.add(resultData[i].getString());
+        }
+        return cnt;
+    }
+
+    public Collection<String> next(final int num) throws JIException {
+        List<String> list = new ArrayList<String>(num);
+        next(list, num);
+        return list;
+    }
+
+    public void skip(final int num) throws JIException {
+        if (num <= 0) {
+            return;
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsInt(num, JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+
+    public void reset() throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        getCOMObject().call(callObject);
+    }
+
+    public EnumString cloneObject() throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        IJIComObject object = (IJIComObject) result[0];
+        return new EnumString(object);
+    }
+
+    public Collection<String> asCollection(final int batchSize) throws JIException {
+        reset();
+
+        List<String> data = new ArrayList<String>();
+        int i = 0;
+        do {
+            i = next(data, batchSize);
+        } while (i == batchSize);
+
+        return data;
+    }
+
+    public Collection<String> asCollection() throws JIException {
+        return asCollection(DEFAULT_BATCH_SIZE);
+    }
+
+}

+ 50 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/EventHandlerImpl.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.IJIComObject;
+import org.jinterop.dcom.core.JIFrameworkHelper;
+import org.openscada.opc.dcom.common.EventHandler;
+
+public class EventHandlerImpl implements EventHandler {
+    private String identifier = null;
+
+    private IJIComObject object = null;
+
+    public String getIdentifier() {
+        return this.identifier;
+    }
+
+    public synchronized IJIComObject getObject() {
+        return this.object;
+    }
+
+    public synchronized void setInfo(final IJIComObject object, final String identifier) {
+        this.object = object;
+        this.identifier = identifier;
+    }
+
+    public synchronized void detach() throws JIException {
+        if (this.object != null && this.identifier != null) {
+            try {
+                JIFrameworkHelper.detachEventHandler(this.object, this.identifier);
+            } finally {
+                this.object = null;
+                this.identifier = null;
+            }
+        }
+    }
+
+}

+ 58 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/Helper.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.IJIComObject;
+import org.jinterop.dcom.core.JICallBuilder;
+import org.jinterop.dcom.core.JIFlags;
+import org.jinterop.dcom.core.JIVariant;
+
+public class Helper {
+    /**
+     * Make the COM call but do not treat S_FALSE as error condition for the whole call
+     *
+     * @param object     the object to make to call on
+     * @param callObject the call object
+     * @return the result of the call
+     * @throws JIException
+     */
+    public static Object[] callRespectSFALSE(final IJIComObject object, final JICallBuilder callObject) throws JIException {
+        try {
+            return object.call(callObject);
+        } catch (JIException e) {
+            if (e.getErrorCode() != org.openscada.opc.dcom.common.Constants.S_FALSE) {
+                throw e;
+            }
+            return callObject.getResultsInCaseOfException();
+        }
+    }
+
+    /**
+     * Perform some fixes on the variant when writing it to OPC items. This method
+     * only changes control information on the variant and not the value itself!
+     *
+     * @param value the value to fix
+     * @return the fixed value
+     * @throws JIException In case something goes wrong
+     */
+    public static JIVariant fixVariant(final JIVariant value) throws JIException {
+        if (value.isArray()) {
+            if (value.getObjectAsArray().getArrayInstance() instanceof Boolean[]) {
+                value.setFlag(JIFlags.FLAG_REPRESENTATION_VARIANT_BOOL);
+            }
+        }
+        return value;
+    }
+}

+ 83 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/common/impl/OPCCommon.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.common.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class OPCCommon extends BaseCOMObject {
+    public OPCCommon(final IJIComObject opcObject) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcObject.queryInterface(org.openscada.opc.dcom.common.Constants.IOPCCommon_IID));
+    }
+
+    public void setLocaleID(final int localeID) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsInt(localeID, JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+
+    public int getLocaleID() throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addOutParamAsObject(Integer.class, JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+        return (Integer) result[0];
+    }
+
+    public String getErrorString(final int errorCode, final int localeID) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addInParamAsInt(errorCode, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(localeID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+        return ((JIString) ((JIPointer) result[0]).getReferent()).getString();
+    }
+
+    public void setClientName(final String clientName) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(4);
+
+        callObject.addInParamAsString(clientName, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+
+        getCOMObject().call(callObject);
+    }
+
+    public Collection<Integer> queryAvailableLocaleIDs() throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        JIArray resultArray = (JIArray) ((JIPointer) result[1]).getReferent();
+        Integer[] intArray = (Integer[]) resultArray.getArrayInstance();
+
+        return Arrays.asList(intArray);
+    }
+
+}

+ 36 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/Constants.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public interface Constants extends org.openscada.opc.dcom.common.Constants {
+    public static final String IOPCServer_IID = "39C13A4D-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCGroupStateMgt_IID = "39C13A50-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCBrowse_IID = "39227004-A18F-4B57-8B0A-5235670F4468";
+
+    public static final String IOPCBrowseServerAddressSpace_IID = "39C13A4F-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCItemMgt_IID = "39C13A54-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCItemProperties_IID = "39C13A72-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCItemIO_IID = "85C0B427-2893-4CBC-BD78-E5FC5146F08F";
+
+    public static final String IOPCDataCallback_IID = "39C13A70-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCAsyncIO2_IID = "39C13A71-011E-11D0-9675-0020AFD8ADB3";
+
+    public static final String IOPCSyncIO_IID = "39C13A52-011E-11D0-9675-0020AFD8ADB3";
+}

+ 27 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/IOPCDataCallback.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.openscada.opc.dcom.common.KeyedResultSet;
+import org.openscada.opc.dcom.common.ResultSet;
+
+public interface IOPCDataCallback {
+    public void dataChange(int transactionId, int serverGroupHandle, int masterQuality, int masterErrorCode, KeyedResultSet<Integer, ValueData> result);
+
+    public void readComplete(int transactionId, int serverGroupHandle, int masterQuality, int masterErrorCode, KeyedResultSet<Integer, ValueData> result);
+
+    public void writeComplete(int transactionId, int serverGroupHandle, int masterErrorCode, ResultSet<Integer> result);
+
+    public void cancelComplete(int transactionId, int serverGroupHandle);
+}

+ 42 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/IORequest.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public class IORequest {
+    private String itemID;
+
+    private int maxAge;
+
+    public IORequest(final String itemID, final int maxAge) {
+        this.itemID = itemID;
+        this.maxAge = maxAge;
+    }
+
+    public String getItemID() {
+        return this.itemID;
+    }
+
+    public void setItemID(final String itemID) {
+        this.itemID = itemID;
+    }
+
+    public int getMaxAge() {
+        return this.maxAge;
+    }
+
+    public void setMaxAge(final int maxAge) {
+        this.maxAge = maxAge;
+    }
+
+}

+ 44 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCBROWSEDIRECTION.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public enum OPCBROWSEDIRECTION {
+    OPC_BROWSE_UP(1),
+    OPC_BROWSE_DOWN(2),
+    OPC_BROWSE_TO(3),
+    OPC_BROWSE_UNKNOWN(0);
+
+    private int _id;
+
+    private OPCBROWSEDIRECTION(final int id) {
+        this._id = id;
+    }
+
+    public int id() {
+        return this._id;
+    }
+
+    public static OPCBROWSEDIRECTION fromID(final int id) {
+        switch (id) {
+            case 1:
+                return OPC_BROWSE_UP;
+            case 2:
+                return OPC_BROWSE_DOWN;
+            case 3:
+                return OPC_BROWSE_TO;
+            default:
+                return OPC_BROWSE_UNKNOWN;
+        }
+    }
+}

+ 44 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCBROWSETYPE.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public enum OPCBROWSETYPE {
+    OPC_BRANCH(1),
+    OPC_LEAF(2),
+    OPC_FLAT(3),
+    OPC_UNKNOWN(0);
+
+    private int _id;
+
+    private OPCBROWSETYPE(final int id) {
+        this._id = id;
+    }
+
+    public int id() {
+        return this._id;
+    }
+
+    public static OPCBROWSETYPE fromID(final int id) {
+        switch (id) {
+            case 1:
+                return OPC_BRANCH;
+            case 2:
+                return OPC_LEAF;
+            case 3:
+                return OPC_FLAT;
+            default:
+                return OPC_UNKNOWN;
+        }
+    }
+}

+ 41 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCDATASOURCE.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public enum OPCDATASOURCE {
+    OPC_DS_CACHE(1),
+    OPC_DS_DEVICE(2),
+    OPC_DS_UNKNOWN(0);
+
+    private int _id;
+
+    private OPCDATASOURCE(final int id) {
+        this._id = id;
+    }
+
+    public int id() {
+        return this._id;
+    }
+
+    public static OPCDATASOURCE fromID(final int id) {
+        switch (id) {
+            case 1:
+                return OPC_DS_CACHE;
+            case 2:
+                return OPC_DS_DEVICE;
+            default:
+                return OPC_DS_UNKNOWN;
+        }
+    }
+}

+ 53 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCENUMSCOPE.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public enum OPCENUMSCOPE {
+    OPC_ENUM_PRIVATE_CONNECTIONS(1),
+    OPC_ENUM_PUBLIC_CONNECTIONS(2),
+    OPC_ENUM_ALL_CONNECTIONS(3),
+    OPC_ENUM_PRIVATE(4),
+    OPC_ENUM_PUBLIC(5),
+    OPC_ENUM_ALL(6),
+    OPC_ENUM_UNKNOWN(0);
+
+    private int _id;
+
+    private OPCENUMSCOPE(final int id) {
+        this._id = id;
+    }
+
+    public int id() {
+        return this._id;
+    }
+
+    public static OPCENUMSCOPE fromID(final int id) {
+        switch (id) {
+            case 1:
+                return OPC_ENUM_PRIVATE_CONNECTIONS;
+            case 2:
+                return OPC_ENUM_PUBLIC_CONNECTIONS;
+            case 3:
+                return OPC_ENUM_ALL_CONNECTIONS;
+            case 4:
+                return OPC_ENUM_PRIVATE;
+            case 5:
+                return OPC_ENUM_PUBLIC;
+            case 6:
+                return OPC_ENUM_ALL;
+            default:
+                return OPC_ENUM_UNKNOWN;
+        }
+    }
+}

+ 96 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCGroupState.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public class OPCGroupState {
+    private int _updateRate = 1000;
+
+    private boolean _active = true;
+
+    private String _name = "";
+
+    private int _timeBias = 0;
+
+    private float _percentDeadband = 0.0f;
+
+    private int _localeID = 0;
+
+    private int _clientHandle = 0;
+
+    private int _serverHandle = 0;
+
+    public boolean isActive() {
+        return this._active;
+    }
+
+    public void setActive(final boolean active) {
+        this._active = active;
+    }
+
+    public int getClientHandle() {
+        return this._clientHandle;
+    }
+
+    public void setClientHandle(final int clientHandle) {
+        this._clientHandle = clientHandle;
+    }
+
+    public int getLocaleID() {
+        return this._localeID;
+    }
+
+    public void setLocaleID(final int localeID) {
+        this._localeID = localeID;
+    }
+
+    public String getName() {
+        return this._name;
+    }
+
+    public void setName(final String name) {
+        this._name = name;
+    }
+
+    public float getPercentDeadband() {
+        return this._percentDeadband;
+    }
+
+    public void setPercentDeadband(final float percentDeadband) {
+        this._percentDeadband = percentDeadband;
+    }
+
+    public int getServerHandle() {
+        return this._serverHandle;
+    }
+
+    public void setServerHandle(final int serverHandle) {
+        this._serverHandle = serverHandle;
+    }
+
+    public int getTimeBias() {
+        return this._timeBias;
+    }
+
+    public void setTimeBias(final int timeBias) {
+        this._timeBias = timeBias;
+    }
+
+    public int getUpdateRate() {
+        return this._updateRate;
+    }
+
+    public void setUpdateRate(final int updateRate) {
+        this._updateRate = updateRate;
+    }
+}

+ 100 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCITEMDEF.java

@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+
+public class OPCITEMDEF {
+    private String accessPath = "";
+
+    private String itemID = "";
+
+    private boolean active = true;
+
+    private int clientHandle;
+
+    private short requestedDataType = JIVariant.VT_EMPTY;
+
+    private short reserved;
+
+    public String getAccessPath() {
+        return this.accessPath;
+    }
+
+    public void setAccessPath(final String accessPath) {
+        this.accessPath = accessPath;
+    }
+
+    public int getClientHandle() {
+        return this.clientHandle;
+    }
+
+    public void setClientHandle(final int clientHandle) {
+        this.clientHandle = clientHandle;
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public void setActive(final boolean active) {
+        this.active = active;
+    }
+
+    public String getItemID() {
+        return this.itemID;
+    }
+
+    public void setItemID(final String itemID) {
+        this.itemID = itemID;
+    }
+
+    public short getRequestedDataType() {
+        return this.requestedDataType;
+    }
+
+    public void setRequestedDataType(final short requestedDataType) {
+        this.requestedDataType = requestedDataType;
+    }
+
+    public short getReserved() {
+        return this.reserved;
+    }
+
+    public void setReserved(final short reserved) {
+        this.reserved = reserved;
+    }
+
+    /**
+     * Convert to structure to a J-Interop structure
+     *
+     * @return the j-interop structe
+     * @throws JIException
+     */
+    public JIStruct toStruct() throws JIException {
+        final JIStruct struct = new JIStruct();
+        struct.addMember(new JIString(getAccessPath(), JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR));
+        struct.addMember(new JIString(getItemID(), JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR));
+        struct.addMember(new Integer(isActive() ? 1 : 0));
+        struct.addMember(Integer.valueOf(getClientHandle()));
+
+        struct.addMember(Integer.valueOf(0)); // blob size
+        struct.addMember(new JIPointer(null)); // blob
+
+        struct.addMember(Short.valueOf(getRequestedDataType()));
+        struct.addMember(Short.valueOf(getReserved()));
+        return struct;
+    }
+}

+ 87 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCITEMRESULT.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIArray;
+import org.jinterop.dcom.core.JIPointer;
+import org.jinterop.dcom.core.JIStruct;
+import org.jinterop.dcom.core.JIVariant;
+
+public class OPCITEMRESULT {
+    private int _serverHandle = 0;
+
+    private short _canonicalDataType = JIVariant.VT_EMPTY;
+
+    private short _reserved = 0;
+
+    private int _accessRights = 0;
+
+    public int getAccessRights() {
+        return this._accessRights;
+    }
+
+    public void setAccessRights(final int accessRights) {
+        this._accessRights = accessRights;
+    }
+
+    public short getCanonicalDataType() {
+        return this._canonicalDataType;
+    }
+
+    public void setCanonicalDataType(final short canonicalDataType) {
+        this._canonicalDataType = canonicalDataType;
+    }
+
+    public short getReserved() {
+        return this._reserved;
+    }
+
+    public void setReserved(final short reserved) {
+        this._reserved = reserved;
+    }
+
+    public int getServerHandle() {
+        return this._serverHandle;
+    }
+
+    public void setServerHandle(final int serverHandle) {
+        this._serverHandle = serverHandle;
+    }
+
+    public static JIStruct getStruct() throws JIException {
+        JIStruct struct = new JIStruct();
+
+        struct.addMember(Integer.class); // Server handle
+        struct.addMember(Short.class); // data type
+        struct.addMember(Short.class); // reserved
+        struct.addMember(Integer.class); // access rights
+        struct.addMember(Integer.class); // blob size
+        // grab the normally unused byte array
+        struct.addMember(new JIPointer(new JIArray(Byte.class, null, 1, true, false)));
+
+        return struct;
+    }
+
+    public static OPCITEMRESULT fromStruct(final JIStruct struct) {
+        OPCITEMRESULT result = new OPCITEMRESULT();
+
+        result.setServerHandle(new Integer((Integer) struct.getMember(0)));
+        result.setCanonicalDataType(new Short((Short) struct.getMember(1)));
+        result.setReserved(new Short((Short) struct.getMember(2)));
+        result.setAccessRights(new Integer((Integer) struct.getMember(3)));
+
+        return result;
+    }
+}

+ 95 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCITEMSTATE.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIStruct;
+import org.jinterop.dcom.core.JIVariant;
+import org.openscada.opc.dcom.common.FILETIME;
+
+public class OPCITEMSTATE {
+    private int _clientHandle = 0;
+
+    private FILETIME _timestamp = null;
+
+    private short _quality = 0;
+
+    private short _reserved = 0;
+
+    private JIVariant _value = null;
+
+    public int getClientHandle() {
+        return this._clientHandle;
+    }
+
+    public void setClientHandle(final int clientHandle) {
+        this._clientHandle = clientHandle;
+    }
+
+    public short getQuality() {
+        return this._quality;
+    }
+
+    public void setQuality(final short quality) {
+        this._quality = quality;
+    }
+
+    public short getReserved() {
+        return this._reserved;
+    }
+
+    public void setReserved(final short reserved) {
+        this._reserved = reserved;
+    }
+
+    public FILETIME getTimestamp() {
+        return this._timestamp;
+    }
+
+    public void setTimestamp(final FILETIME timestamp) {
+        this._timestamp = timestamp;
+    }
+
+    public JIVariant getValue() {
+        return this._value;
+    }
+
+    public void setValue(final JIVariant value) {
+        this._value = value;
+    }
+
+    public static JIStruct getStruct() throws JIException {
+        JIStruct struct = new JIStruct();
+
+        struct.addMember(Integer.class);
+        struct.addMember(FILETIME.getStruct());
+        struct.addMember(Short.class);
+        struct.addMember(Short.class);
+        struct.addMember(JIVariant.class);
+
+        return struct;
+    }
+
+    public static OPCITEMSTATE fromStruct(final JIStruct struct) {
+        OPCITEMSTATE itemState = new OPCITEMSTATE();
+
+        itemState.setClientHandle((Integer) struct.getMember(0));
+        itemState.setTimestamp(FILETIME.fromStruct((JIStruct) struct.getMember(1)));
+        itemState.setQuality((Short) struct.getMember(2));
+        itemState.setReserved((Short) struct.getMember(3));
+        itemState.setValue((JIVariant) struct.getMember(4));
+
+        return itemState;
+    }
+}

+ 41 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCNAMESPACETYPE.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public enum OPCNAMESPACETYPE {
+    OPC_NS_HIERARCHIAL(1),
+    OPC_NS_FLAT(2),
+    OPC_NS_UNKNOWN(0);
+
+    private int _id;
+
+    private OPCNAMESPACETYPE(final int id) {
+        this._id = id;
+    }
+
+    public int id() {
+        return this._id;
+    }
+
+    public static OPCNAMESPACETYPE fromID(final int id) {
+        switch (id) {
+            case 1:
+                return OPC_NS_HIERARCHIAL;
+            case 2:
+                return OPC_NS_FLAT;
+            default:
+                return OPC_NS_UNKNOWN;
+        }
+    }
+}

+ 53 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCSERVERSTATE.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public enum OPCSERVERSTATE {
+    OPC_STATUS_RUNNING(1),
+    OPC_STATUS_FAILED(2),
+    OPC_STATUS_NOCONFIG(3),
+    OPC_STATUS_SUSPENDED(4),
+    OPC_STATUS_TEST(5),
+    OPC_STATUS_COMM_FAULT(6),
+    OPC_STATUS_UNKNOWN(0);
+
+    private int _id;
+
+    private OPCSERVERSTATE(final int id) {
+        this._id = id;
+    }
+
+    public int id() {
+        return this._id;
+    }
+
+    public static OPCSERVERSTATE fromID(final int id) {
+        switch (id) {
+            case 1:
+                return OPC_STATUS_RUNNING;
+            case 2:
+                return OPC_STATUS_FAILED;
+            case 3:
+                return OPC_STATUS_NOCONFIG;
+            case 4:
+                return OPC_STATUS_SUSPENDED;
+            case 5:
+                return OPC_STATUS_TEST;
+            case 6:
+                return OPC_STATUS_COMM_FAULT;
+            default:
+                return OPC_STATUS_UNKNOWN;
+        }
+    }
+}

+ 170 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/OPCSERVERSTATUS.java

@@ -0,0 +1,170 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIFlags;
+import org.jinterop.dcom.core.JIPointer;
+import org.jinterop.dcom.core.JIString;
+import org.jinterop.dcom.core.JIStruct;
+import org.openscada.opc.dcom.common.FILETIME;
+
+public class OPCSERVERSTATUS {
+    private FILETIME _startTime = null;
+
+    private FILETIME _currentTime = null;
+
+    private FILETIME _lastUpdateTime = null;
+
+    private OPCSERVERSTATE _serverState = null;
+
+    private int _groupCount = -1;
+
+    private int _bandWidth = -1;
+
+    private short _majorVersion = -1;
+
+    private short _minorVersion = -1;
+
+    private short _buildNumber = -1;
+
+    private short _reserved = 0;
+
+    private String _vendorInfo = null;
+
+    public int getBandWidth() {
+        return this._bandWidth;
+    }
+
+    public void setBandWidth(final int bandWidth) {
+        this._bandWidth = bandWidth;
+    }
+
+    public short getBuildNumber() {
+        return this._buildNumber;
+    }
+
+    public void setBuildNumber(final short buildNumber) {
+        this._buildNumber = buildNumber;
+    }
+
+    public FILETIME getCurrentTime() {
+        return this._currentTime;
+    }
+
+    public void setCurrentTime(final FILETIME currentTime) {
+        this._currentTime = currentTime;
+    }
+
+    public int getGroupCount() {
+        return this._groupCount;
+    }
+
+    public void setGroupCount(final int groupCount) {
+        this._groupCount = groupCount;
+    }
+
+    public FILETIME getLastUpdateTime() {
+        return this._lastUpdateTime;
+    }
+
+    public void setLastUpdateTime(final FILETIME lastUpdateTime) {
+        this._lastUpdateTime = lastUpdateTime;
+    }
+
+    public short getMajorVersion() {
+        return this._majorVersion;
+    }
+
+    public void setMajorVersion(final short majorVersion) {
+        this._majorVersion = majorVersion;
+    }
+
+    public short getMinorVersion() {
+        return this._minorVersion;
+    }
+
+    public void setMinorVersion(final short minorVersion) {
+        this._minorVersion = minorVersion;
+    }
+
+    public short getReserved() {
+        return this._reserved;
+    }
+
+    public void setReserved(final short reserved) {
+        this._reserved = reserved;
+    }
+
+    public FILETIME getStartTime() {
+        return this._startTime;
+    }
+
+    public void setStartTime(final FILETIME startTime) {
+        this._startTime = startTime;
+    }
+
+    public String getVendorInfo() {
+        return this._vendorInfo;
+    }
+
+    public void setVendorInfo(final String vendorInfo) {
+        this._vendorInfo = vendorInfo;
+    }
+
+    public OPCSERVERSTATE getServerState() {
+        return this._serverState;
+    }
+
+    public void setServerState(final OPCSERVERSTATE dwServerState) {
+        this._serverState = dwServerState;
+    }
+
+    public static JIStruct getStruct() throws JIException {
+        JIStruct struct = new JIStruct();
+
+        struct.addMember(FILETIME.getStruct());
+        struct.addMember(FILETIME.getStruct());
+        struct.addMember(FILETIME.getStruct());
+        struct.addMember(Short.class); // enum: OPCSERVERSTATE
+        struct.addMember(Integer.class);
+        struct.addMember(Integer.class);
+        struct.addMember(Short.class);
+        struct.addMember(Short.class);
+        struct.addMember(Short.class);
+        struct.addMember(Short.class);
+        struct.addMember(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)));
+
+        return struct;
+    }
+
+    public static OPCSERVERSTATUS fromStruct(final JIStruct struct) {
+        OPCSERVERSTATUS status = new OPCSERVERSTATUS();
+
+        status._startTime = FILETIME.fromStruct((JIStruct) struct.getMember(0));
+        status._currentTime = FILETIME.fromStruct((JIStruct) struct.getMember(1));
+        status._lastUpdateTime = FILETIME.fromStruct((JIStruct) struct.getMember(2));
+
+        status._serverState = OPCSERVERSTATE.fromID((Short) struct.getMember(3));
+        status._groupCount = (Integer) struct.getMember(4);
+        status._bandWidth = (Integer) struct.getMember(5);
+        status._majorVersion = (Short) struct.getMember(6);
+        status._minorVersion = (Short) struct.getMember(7);
+        status._buildNumber = (Short) struct.getMember(8);
+        status._reserved = (Short) struct.getMember(9);
+        status._vendorInfo = ((JIString) ((JIPointer) struct.getMember(10)).getReferent()).getString();
+
+        return status;
+    }
+}

+ 46 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/PropertyDescription.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+public class PropertyDescription {
+    private int _id = -1;
+
+    private String _description = "";
+
+    private short _varType = 0;
+
+    public String getDescription() {
+        return this._description;
+    }
+
+    public void setDescription(final String description) {
+        this._description = description;
+    }
+
+    public int getId() {
+        return this._id;
+    }
+
+    public void setId(final int id) {
+        this._id = id;
+    }
+
+    public short getVarType() {
+        return this._varType;
+    }
+
+    public void setVarType(final short varType) {
+        this._varType = varType;
+    }
+}

+ 50 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/ValueData.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.jinterop.dcom.core.JIVariant;
+
+import java.util.Calendar;
+
+public class ValueData {
+    private JIVariant value;
+
+    private short quality;
+
+    private Calendar timestamp;
+
+    public short getQuality() {
+        return this.quality;
+    }
+
+    public void setQuality(final short quality) {
+        this.quality = quality;
+    }
+
+    public Calendar getTimestamp() {
+        return this.timestamp;
+    }
+
+    public void setTimestamp(final Calendar timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public JIVariant getValue() {
+        return this.value;
+    }
+
+    public void setValue(final JIVariant value) {
+        this.value = value;
+    }
+}

+ 62 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/WriteRequest.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da;
+
+import org.jinterop.dcom.core.JIVariant;
+
+/**
+ * Data for a write request to the server
+ *
+ * @author Jens Reimann <jens.reimann@th4-systems.com>
+ */
+public class WriteRequest {
+    private int serverHandle = 0;
+
+    private JIVariant value = JIVariant.EMPTY();
+
+    public WriteRequest() {
+    }
+
+    public WriteRequest(final WriteRequest request) {
+        this.serverHandle = request.serverHandle;
+        this.value = request.value;
+    }
+
+    /**
+     * Create a new write request with pre-fille data
+     *
+     * @param serverHandle the server handle of the item to write to
+     * @param value        the value to write.
+     */
+    public WriteRequest(final int serverHandle, final JIVariant value) {
+        this.serverHandle = serverHandle;
+        this.value = value;
+    }
+
+    public int getServerHandle() {
+        return this.serverHandle;
+    }
+
+    public void setServerHandle(final int serverHandle) {
+        this.serverHandle = serverHandle;
+    }
+
+    public JIVariant getValue() {
+        return this.value;
+    }
+
+    public void setValue(final JIVariant value) {
+        this.value = value;
+    }
+}

+ 116 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCAsyncIO2.java

@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.Result;
+import org.openscada.opc.dcom.common.ResultSet;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.OPCDATASOURCE;
+
+import java.net.UnknownHostException;
+
+public class OPCAsyncIO2 extends BaseCOMObject {
+    public class AsyncResult {
+        private final ResultSet<Integer> result;
+
+        private final Integer cancelId;
+
+        public AsyncResult() {
+            super();
+            this.result = new ResultSet<Integer>();
+            this.cancelId = null;
+        }
+
+        public AsyncResult(final ResultSet<Integer> result, final Integer cancelId) {
+            super();
+            this.result = result;
+            this.cancelId = cancelId;
+        }
+
+        public Integer getCancelId() {
+            return this.cancelId;
+        }
+
+        public ResultSet<Integer> getResult() {
+            return this.result;
+        }
+    }
+
+    public OPCAsyncIO2(final IJIComObject opcAsyncIO2) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcAsyncIO2.queryInterface(Constants.IOPCAsyncIO2_IID));
+    }
+
+    public void setEnable(final boolean state) throws JIException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(4);
+
+        callObject.addInParamAsInt(state ? 1 : 0, JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+
+    public int refresh(final OPCDATASOURCE dataSource, final int transactionID) throws JIException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsShort((short) dataSource.id(), JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(transactionID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+
+        final Object result[] = getCOMObject().call(callObject);
+
+        return (Integer) result[0];
+    }
+
+    public void cancel(final int cancelId) throws JIException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addInParamAsInt(cancelId, JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+
+    public AsyncResult read(final int transactionId, final Integer... serverHandles) throws JIException {
+        if (serverHandles == null || serverHandles.length == 0) {
+            return new AsyncResult();
+        }
+
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(transactionId, JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        final Object[] result = getCOMObject().call(callObject);
+
+        final Integer cancelId = (Integer) result[0];
+        final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+
+        final ResultSet<Integer> resultSet = new ResultSet<Integer>();
+
+        for (int i = 0; i < serverHandles.length; i++) {
+            resultSet.add(new Result<Integer>(serverHandles[i], errorCodes[i]));
+        }
+
+        return new AsyncResult(resultSet, cancelId);
+    }
+}

+ 29 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCBrowse.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import java.net.UnknownHostException;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.IJIComObject;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.da.Constants;
+
+public class OPCBrowse extends BaseCOMObject
+{
+    public OPCBrowse ( final IJIComObject opcServer ) throws IllegalArgumentException, UnknownHostException, JIException
+    {
+        super ( opcServer.queryInterface ( Constants.IOPCBrowse_IID ) );
+    }
+}

+ 142 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCBrowseServerAddressSpace.java

@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.common.impl.EnumString;
+import org.openscada.opc.dcom.common.impl.Helper;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.OPCBROWSEDIRECTION;
+import org.openscada.opc.dcom.da.OPCBROWSETYPE;
+import org.openscada.opc.dcom.da.OPCNAMESPACETYPE;
+
+import java.net.UnknownHostException;
+
+/**
+ * Implementation for <code>IOPCBrowseServerAddressSpace</code>
+ *
+ * @author Jens Reimann <jens.reimann@th4-systems.com>
+ */
+public class OPCBrowseServerAddressSpace extends BaseCOMObject {
+    public OPCBrowseServerAddressSpace(final IJIComObject opcServer) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcServer.queryInterface(Constants.IOPCBrowseServerAddressSpace_IID));
+    }
+
+    /**
+     * Get the information how the namespace is organized
+     *
+     * @return the organization of the namespace
+     * @throws JIException
+     */
+    public OPCNAMESPACETYPE queryOrganization() throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addOutParamAsType(Short.class, JIFlags.FLAG_NULL);
+
+        Object result[] = getCOMObject().call(callObject);
+
+        return OPCNAMESPACETYPE.fromID((Short) result[0]);
+    }
+
+    /**
+     * Direct the browser to another position
+     * <p>
+     * Depending on the <em>direction</em> the new position will be set based on the provided
+     * position information. If the direction is {@link OPCBROWSEDIRECTION#OPC_BROWSE_TO} then
+     * the <em>position</em> is the item to go to. If the direction is {@link OPCBROWSEDIRECTION#OPC_BROWSE_DOWN}
+     * the browser will descent into the tree down (not to) the branch item in <em>position</em>.
+     * Passing {@link OPCBROWSEDIRECTION#OPC_BROWSE_UP} won't need a <em>position</em> (pass <code>null</code>)
+     * and will ascent in the tree one level.
+     * <p>
+     * Passing {@link OPCBROWSEDIRECTION#OPC_BROWSE_TO} and <code>null</code> as position will
+     * go to the first root entry of the namespace.
+     *
+     * @param position  The item position reference for the direction
+     * @param direction The direction to go based on the position
+     * @throws JIException
+     */
+    public void changePosition(final String position, final OPCBROWSEDIRECTION direction) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsShort((short) direction.id(), JIFlags.FLAG_NULL);
+        callObject.addInParamAsString(position, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+
+        getCOMObject().call(callObject);
+
+    }
+
+    public EnumString browse(final OPCBROWSETYPE browseType, final String filterCriteria, final int accessRights, final int dataType) throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsShort((short) browseType.id(), JIFlags.FLAG_NULL);
+        callObject.addInParamAsString(filterCriteria, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addInParamAsShort((short) dataType, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(accessRights, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        return new EnumString((IJIComObject) result[0]);
+    }
+
+    /**
+     * Return the possible access paths for an item
+     *
+     * @param itemID the item to query
+     * @return A string enumerator for the possible access paths
+     * @throws JIException
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     */
+    public EnumString browseAccessPaths(final String itemID) throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(4);
+
+        callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        return new EnumString((IJIComObject) result[0]);
+    }
+
+    /**
+     * Get the complete item id from an item at the local position.
+     * <p>
+     * Browsing a hierarchical namespace the browse method will return items based on the
+     * local level in the namespace. So actually only the last part of the item ID hierarchy
+     * is returned. In order to convert this to the full item ID one can use this method. It
+     * will only work if the browser is still at the position in question.
+     *
+     * @param item the local item
+     * @return the complete item ID
+     * @throws JIException
+     */
+    public String getItemID(final String item) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addInParamAsString(item, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        return ((JIString) ((JIPointer) result[0]).getReferent()).getString();
+    }
+}

+ 214 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCDataCallback.java

@@ -0,0 +1,214 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.*;
+import org.openscada.opc.dcom.common.impl.EventHandlerImpl;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.IOPCDataCallback;
+import org.openscada.opc.dcom.da.ValueData;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class OPCDataCallback extends EventHandlerImpl {
+    private IOPCDataCallback callback = null;
+
+    private JILocalCoClass coClass = null;
+
+    public OPCDataCallback() {
+        super();
+    }
+
+    public Object[] OnDataChange(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final int count, final JIArray clientHandles, final JIArray values, final JIArray qualities, final JIArray timestamps, final JIArray errors) {
+        final IOPCDataCallback callback = this.callback;
+        if (callback == null) {
+            return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+        }
+
+        // get arrays for more readable code later ;-)
+        final Integer[] errorCodes = (Integer[]) errors.getArrayInstance();
+        final Integer[] itemHandles = (Integer[]) clientHandles.getArrayInstance();
+        final Short[] qualitiesArray = (Short[]) qualities.getArrayInstance();
+        final JIVariant[] valuesArray = (JIVariant[]) values.getArrayInstance();
+        final JIStruct[] timestampArray = (JIStruct[]) timestamps.getArrayInstance();
+
+        // create result data
+        final KeyedResultSet<Integer, ValueData> result = new KeyedResultSet<Integer, ValueData>();
+        for (int i = 0; i < count; i++) {
+            final ValueData vd = new ValueData();
+            vd.setQuality(qualitiesArray[i]);
+            vd.setTimestamp(FILETIME.fromStruct(timestampArray[i]).asCalendar());
+            vd.setValue(valuesArray[i]);
+            result.add(new KeyedResult<Integer, ValueData>(itemHandles[i], vd, errorCodes[i]));
+        }
+
+        // fire event
+        try {
+            callback.dataChange(transactionId, serverGroupHandle, masterQuality, masterErrorCode, result);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+        }
+
+        // The client must always return S_OK
+        return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+    }
+
+    public synchronized Object[] OnReadComplete(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final int count, final JIArray clientHandles, final JIArray values, final JIArray qualities, final JIArray timestamps, final JIArray errors) {
+        if (this.callback == null) {
+            return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+        }
+
+        // get arrays for more readable code later ;-)
+        final Integer[] errorCodes = (Integer[]) errors.getArrayInstance();
+        final Integer[] itemHandles = (Integer[]) clientHandles.getArrayInstance();
+        final Short[] qualitiesArray = (Short[]) qualities.getArrayInstance();
+        final JIVariant[] valuesArray = (JIVariant[]) values.getArrayInstance();
+        final JIStruct[] timestampArray = (JIStruct[]) timestamps.getArrayInstance();
+
+        // create result data
+        final KeyedResultSet<Integer, ValueData> result = new KeyedResultSet<Integer, ValueData>();
+        for (int i = 0; i < count; i++) {
+            final ValueData vd = new ValueData();
+            vd.setQuality(qualitiesArray[i]);
+            vd.setTimestamp(FILETIME.fromStruct(timestampArray[i]).asCalendar());
+            vd.setValue(valuesArray[i]);
+            result.add(new KeyedResult<Integer, ValueData>(itemHandles[i], vd, errorCodes[i]));
+        }
+
+        // fire event
+        try {
+            this.callback.readComplete(transactionId, serverGroupHandle, masterQuality, masterErrorCode, result);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+        }
+
+        // The client must always return S_OK
+        return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+    }
+
+    public synchronized Object[] OnWriteComplete(final int transactionId, final int serverGroupHandle, final int masterErrorCode, final int count, final JIArray clientHandles, final JIArray errors) {
+        if (this.callback == null) {
+            return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+        }
+
+        // get arrays for more readable code later ;-)
+        final Integer[] errorCodes = (Integer[]) errors.getArrayInstance();
+        final Integer[] itemHandles = (Integer[]) clientHandles.getArrayInstance();
+
+        // create result data
+        final ResultSet<Integer> result = new ResultSet<Integer>();
+        for (int i = 0; i < count; i++) {
+            result.add(new Result<Integer>(itemHandles[i], errorCodes[i]));
+        }
+
+        // fire event
+        try {
+            this.callback.writeComplete(transactionId, serverGroupHandle, masterErrorCode, result);
+        } catch (final Throwable e) {
+            e.printStackTrace();
+        }
+
+        // The client must always return S_OK
+        return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+    }
+
+    public synchronized Object[] OnCancelComplete(final int transactionId, final int serverGroupHandle) {
+        if (this.callback == null) {
+            return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+        }
+
+        this.callback.cancelComplete(transactionId, serverGroupHandle);
+
+        // The client must always return S_OK
+        return new Object[]{org.openscada.opc.dcom.common.Constants.S_OK};
+    }
+
+    public synchronized JILocalCoClass getCoClass() throws JIException {
+        if (this.coClass != null) {
+            return this.coClass;
+        }
+
+        this.coClass = new JILocalCoClass(new JILocalInterfaceDefinition(Constants.IOPCDataCallback_IID, false), this, false);
+
+        JILocalParamsDescriptor params;
+        JILocalMethodDescriptor method;
+
+        // OnDataChange
+        params = new JILocalParamsDescriptor();
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // trans id
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // group handle
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // master quality
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // master error
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL); // count
+        params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); // item handles
+        params.addInParamAsObject(new JIArray(JIVariant.class, null, 1, true), JIFlags.FLAG_NULL); // values
+        params.addInParamAsObject(new JIArray(Short.class, null, 1, true), JIFlags.FLAG_NULL); // qualities
+        params.addInParamAsObject(new JIArray(FILETIME.getStruct(), null, 1, true), JIFlags.FLAG_NULL); // timestamps
+        params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL); // errors
+
+        method = new JILocalMethodDescriptor("OnDataChange", params);
+        this.coClass.getInterfaceDefinition().addMethodDescriptor(method);
+
+        // OnReadComplete
+        params = new JILocalParamsDescriptor();
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(JIVariant.class, null, 1, true), JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(Short.class, null, 1, true), JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(FILETIME.getStruct(), null, 1, true), JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL);
+        method = new JILocalMethodDescriptor("OnReadComplete", params);
+        this.coClass.getInterfaceDefinition().addMethodDescriptor(method);
+
+        // OnWriteComplete
+        params = new JILocalParamsDescriptor();
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL);
+        params.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL);
+        method = new JILocalMethodDescriptor("OnWriteComplete", params);
+        this.coClass.getInterfaceDefinition().addMethodDescriptor(method);
+
+        // OnCancelComplete
+        params = new JILocalParamsDescriptor();
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        params.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        method = new JILocalMethodDescriptor("OnCancelComplete", params);
+        this.coClass.getInterfaceDefinition().addMethodDescriptor(method);
+
+        // Add supported event interfaces
+        final List<String> eventInterfaces = new LinkedList<String>();
+        eventInterfaces.add(Constants.IOPCDataCallback_IID);
+        this.coClass.setSupportedEventInterfaces(eventInterfaces);
+
+        return this.coClass;
+    }
+
+    public void setCallback(final IOPCDataCallback callback) {
+        this.callback = callback;
+    }
+
+    public IOPCDataCallback getCallback() {
+        return this.callback;
+    }
+}

+ 178 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCGroupStateMgt.java

@@ -0,0 +1,178 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.jinterop.dcom.impls.JIObjectFactory;
+import org.openscada.opc.dcom.common.EventHandler;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.IOPCDataCallback;
+import org.openscada.opc.dcom.da.OPCGroupState;
+
+import java.net.UnknownHostException;
+
+/**
+ * Implementation of <code>IOPCGroupStateMgt</code>
+ *
+ * @author Jens Reimann <jens.reimann@th4-systems.com>
+ */
+public class OPCGroupStateMgt extends BaseCOMObject {
+    public OPCGroupStateMgt(final IJIComObject opcGroup) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcGroup.queryInterface(Constants.IOPCGroupStateMgt_IID));
+    }
+
+    public OPCGroupState getState() throws JIException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Boolean.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Float.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+
+        final Object result[] = getCOMObject().call(callObject);
+
+        final OPCGroupState state = new OPCGroupState();
+        state.setUpdateRate((Integer) result[0]);
+        state.setActive((Boolean) result[1]);
+        state.setName(((JIString) ((JIPointer) result[2]).getReferent()).getString());
+        state.setTimeBias((Integer) result[3]);
+        state.setPercentDeadband((Float) result[4]);
+        state.setLocaleID((Integer) result[5]);
+        state.setClientHandle((Integer) result[6]);
+        state.setServerHandle((Integer) result[7]);
+
+        return state;
+    }
+
+    /**
+     * Set the group state Leaving any of the parameters <code>null</code> will keep the current value untouched.
+     *
+     * @param requestedUpdateRate the requested update rate
+     * @param active              Flag if the group is active or not
+     * @param timeBias            The time bias
+     * @param percentDeadband     the deadband percent
+     * @param localeID            the locale ID
+     * @param clientHandle        the client handle
+     * @return the granted update rate
+     * @throws JIException
+     */
+    public int setState(final Integer requestedUpdateRate, final Boolean active, final Integer timeBias, final Float percentDeadband, final Integer localeID, final Integer clientHandle) throws JIException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsPointer(new JIPointer(requestedUpdateRate), JIFlags.FLAG_NULL);
+        if (active != null) {
+            callObject.addInParamAsPointer(new JIPointer(Integer.valueOf(active.booleanValue() ? 1 : 0)), JIFlags.FLAG_NULL);
+        } else {
+            callObject.addInParamAsPointer(new JIPointer(null), JIFlags.FLAG_NULL);
+        }
+        callObject.addInParamAsPointer(new JIPointer(timeBias), JIFlags.FLAG_NULL);
+        callObject.addInParamAsPointer(new JIPointer(percentDeadband), JIFlags.FLAG_NULL);
+        callObject.addInParamAsPointer(new JIPointer(localeID), JIFlags.FLAG_NULL);
+        callObject.addInParamAsPointer(new JIPointer(clientHandle), JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+
+        final Object[] result = getCOMObject().call(callObject);
+
+        return (Integer) result[0];
+    }
+
+    public OPCItemMgt getItemManagement() throws JIException {
+        return new OPCItemMgt(getCOMObject());
+    }
+
+    /**
+     * Rename to group
+     *
+     * @param name the new name
+     * @throws JIException
+     */
+    public void setName(final String name) throws JIException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+
+        getCOMObject().call(callObject);
+    }
+
+    /**
+     * Clone the group
+     *
+     * @param name the name of the cloned group
+     * @return The cloned group
+     * @throws JIException
+     * @throws UnknownHostException
+     * @throws IllegalArgumentException
+     */
+    public OPCGroupStateMgt clone(final String name) throws JIException, IllegalArgumentException, UnknownHostException {
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addInParamAsUUID(Constants.IOPCGroupStateMgt_IID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        final Object[] result = getCOMObject().call(callObject);
+        return new OPCGroupStateMgt((IJIComObject) result[0]);
+    }
+
+    /**
+     * Attach a new callback to the group
+     *
+     * @param callback The callback to attach
+     * @return The event handler information
+     * @throws JIException
+     */
+    public EventHandler attach(final IOPCDataCallback callback) throws JIException {
+        final OPCDataCallback callbackObject = new OPCDataCallback();
+
+        callbackObject.setCallback(callback);
+
+        // sync the callback object so that no calls get through the callback
+        // until the callback information is set
+        // If happens in some cases that the callback is triggered before
+        // the method attachEventHandler returns.
+        synchronized (callbackObject) {
+            final String id = JIFrameworkHelper.attachEventHandler(getCOMObject(), Constants.IOPCDataCallback_IID, JIObjectFactory.buildObject(getCOMObject().getAssociatedSession(), callbackObject.getCoClass()));
+
+            callbackObject.setInfo(getCOMObject(), id);
+        }
+        return callbackObject;
+    }
+
+    public OPCAsyncIO2 getAsyncIO2() {
+        try {
+            return new OPCAsyncIO2(getCOMObject());
+        } catch (final Exception e) {
+            return null;
+        }
+    }
+
+    public OPCSyncIO getSyncIO() {
+        try {
+            return new OPCSyncIO(getCOMObject());
+        } catch (final Exception e) {
+            return null;
+        }
+    }
+}

+ 56 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCItemIO.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.FILETIME;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.IORequest;
+
+import java.net.UnknownHostException;
+
+public class OPCItemIO extends BaseCOMObject {
+    public OPCItemIO(final IJIComObject opcItemIO) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcItemIO.queryInterface(Constants.IOPCItemIO_IID));
+    }
+
+    public void read(final IORequest[] requests) throws JIException {
+        if (requests.length == 0) {
+            return;
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        JIString itemIDs[] = new JIString[requests.length];
+        Integer maxAges[] = new Integer[requests.length];
+        for (int i = 0; i < requests.length; i++) {
+            itemIDs[i] = new JIString(requests[i].getItemID(), JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+            maxAges[i] = new Integer(requests[i].getMaxAge());
+        }
+
+        callObject.addInParamAsInt(requests.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(itemIDs, true), JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(maxAges, true), JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(JIVariant.class, null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(FILETIME.getStruct(), null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+}

+ 180 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCItemMgt.java

@@ -0,0 +1,180 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.KeyedResult;
+import org.openscada.opc.dcom.common.KeyedResultSet;
+import org.openscada.opc.dcom.common.Result;
+import org.openscada.opc.dcom.common.ResultSet;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.common.impl.Helper;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.OPCITEMDEF;
+import org.openscada.opc.dcom.da.OPCITEMRESULT;
+
+public class OPCItemMgt extends BaseCOMObject {
+    public OPCItemMgt(final IJIComObject opcGroup) throws JIException {
+        super(opcGroup.queryInterface(Constants.IOPCItemMgt_IID));
+    }
+
+    public KeyedResultSet<OPCITEMDEF, OPCITEMRESULT> validate(final OPCITEMDEF... items) throws JIException {
+        if (items.length == 0) {
+            return new KeyedResultSet<OPCITEMDEF, OPCITEMRESULT>();
+        }
+
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        final JIStruct struct[] = new JIStruct[items.length];
+        for (int i = 0; i < items.length; i++) {
+            struct[i] = items[i].toStruct();
+        }
+        final JIArray itemArray = new JIArray(struct, true);
+
+        callObject.addInParamAsInt(items.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(itemArray, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(0, JIFlags.FLAG_NULL); // don't update blobs
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(OPCITEMRESULT.getStruct(), null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        final Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        final JIStruct[] results = (JIStruct[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+
+        final KeyedResultSet<OPCITEMDEF, OPCITEMRESULT> resultList = new KeyedResultSet<OPCITEMDEF, OPCITEMRESULT>(items.length);
+        for (int i = 0; i < items.length; i++) {
+            final OPCITEMRESULT itemResult = OPCITEMRESULT.fromStruct(results[i]);
+            final KeyedResult<OPCITEMDEF, OPCITEMRESULT> resultEntry = new KeyedResult<OPCITEMDEF, OPCITEMRESULT>(items[i], itemResult, errorCodes[i]);
+            resultList.add(resultEntry);
+        }
+
+        return resultList;
+    }
+
+    public KeyedResultSet<OPCITEMDEF, OPCITEMRESULT> add(final OPCITEMDEF... items) throws JIException {
+        if (items.length == 0) {
+            return new KeyedResultSet<OPCITEMDEF, OPCITEMRESULT>();
+        }
+
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        final JIStruct struct[] = new JIStruct[items.length];
+        for (int i = 0; i < items.length; i++) {
+            struct[i] = items[i].toStruct();
+        }
+        final JIArray itemArray = new JIArray(struct, true);
+
+        callObject.addInParamAsInt(items.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(itemArray, JIFlags.FLAG_NULL);
+
+        /*
+        callObject.addOutParamAsObject ( new JIPointer ( new JIArray ( OPCITEMRESULT.getStruct (), null, 1, true ) ),
+                JIFlags.FLAG_NULL );
+        callObject.addOutParamAsObject ( new JIPointer ( new JIArray ( Integer.class, null, 1, true ) ),
+                JIFlags.FLAG_NULL );
+                */
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(OPCITEMRESULT.getStruct(), null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        final Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        final JIStruct[] results = (JIStruct[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+
+        final KeyedResultSet<OPCITEMDEF, OPCITEMRESULT> resultList = new KeyedResultSet<OPCITEMDEF, OPCITEMRESULT>(items.length);
+        for (int i = 0; i < items.length; i++) {
+            final OPCITEMRESULT itemResult = OPCITEMRESULT.fromStruct(results[i]);
+            final KeyedResult<OPCITEMDEF, OPCITEMRESULT> resultEntry = new KeyedResult<OPCITEMDEF, OPCITEMRESULT>(items[i], itemResult, errorCodes[i]);
+            resultList.add(resultEntry);
+        }
+
+        return resultList;
+    }
+
+    public ResultSet<Integer> remove(final Integer... serverHandles) throws JIException {
+        if (serverHandles.length == 0) {
+            return new ResultSet<Integer>();
+        }
+
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        final Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        final ResultSet<Integer> results = new ResultSet<Integer>(serverHandles.length);
+        for (int i = 0; i < serverHandles.length; i++) {
+            results.add(new Result<Integer>(serverHandles[i], errorCodes[i]));
+        }
+        return results;
+    }
+
+    public ResultSet<Integer> setActiveState(final boolean state, final Integer... items) throws JIException {
+        if (items.length == 0) {
+            return new ResultSet<Integer>();
+        }
+
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addInParamAsInt(items.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(items, true), JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(state ? 1 : 0, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        final Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        final ResultSet<Integer> results = new ResultSet<Integer>(items.length);
+        for (int i = 0; i < items.length; i++) {
+            results.add(new Result<Integer>(items[i], errorCodes[i]));
+        }
+        return results;
+    }
+
+    public ResultSet<Integer> setClientHandles(final Integer[] serverHandles, final Integer[] clientHandles) throws JIException {
+        if (serverHandles.length != clientHandles.length) {
+            throw new JIException(0, "Array sizes don't match");
+        }
+        if (serverHandles.length == 0) {
+            return new ResultSet<Integer>();
+        }
+
+        final JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(4);
+
+        callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(clientHandles, true), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        final Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        final Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        final ResultSet<Integer> results = new ResultSet<Integer>(serverHandles.length);
+        for (int i = 0; i < serverHandles.length; i++) {
+            results.add(new Result<Integer>(serverHandles[i], errorCodes[i]));
+        }
+        return results;
+    }
+
+}

+ 130 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCItemProperties.java

@@ -0,0 +1,130 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.KeyedResult;
+import org.openscada.opc.dcom.common.KeyedResultSet;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.common.impl.Helper;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.PropertyDescription;
+
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+public class OPCItemProperties extends BaseCOMObject {
+    public OPCItemProperties(final IJIComObject opcItemProperties) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcItemProperties.queryInterface(Constants.IOPCItemProperties_IID));
+    }
+
+    public Collection<PropertyDescription> queryAvailableProperties(final String itemID) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_BSTR), null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Short.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        Object result[] = getCOMObject().call(callObject);
+
+        List<PropertyDescription> properties = new LinkedList<PropertyDescription>();
+
+        int len = (Integer) result[0];
+        Integer[] ids = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+        JIString[] descriptions = (JIString[]) ((JIArray) ((JIPointer) result[2]).getReferent()).getArrayInstance();
+        Short[] variableTypes = (Short[]) ((JIArray) ((JIPointer) result[3]).getReferent()).getArrayInstance();
+
+        for (int i = 0; i < len; i++) {
+            PropertyDescription pd = new PropertyDescription();
+            pd.setId(ids[i]);
+            pd.setDescription(descriptions[i].getString());
+            pd.setVarType(variableTypes[i]);
+            properties.add(pd);
+        }
+        return properties;
+    }
+
+    public KeyedResultSet<Integer, JIVariant> getItemProperties(final String itemID, final int... properties) throws JIException {
+        if (properties.length == 0) {
+            return new KeyedResultSet<Integer, JIVariant>();
+        }
+
+        Integer[] ids = new Integer[properties.length];
+        for (int i = 0; i < properties.length; i++) {
+            ids[i] = properties[i];
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addInParamAsInt(properties.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(ids, true), JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(JIVariant.class, null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        JIVariant[] values = (JIVariant[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+
+        KeyedResultSet<Integer, JIVariant> results = new KeyedResultSet<Integer, JIVariant>();
+        for (int i = 0; i < properties.length; i++) {
+            results.add(new KeyedResult<Integer, JIVariant>(properties[i], values[i], errorCodes[i]));
+        }
+        return results;
+    }
+
+    public KeyedResultSet<Integer, String> lookupItemIDs(final String itemID, final int... properties) throws JIException {
+        if (properties.length == 0) {
+            return new KeyedResultSet<Integer, String>();
+        }
+
+        Integer[] ids = new Integer[properties.length];
+        for (int i = 0; i < properties.length; i++) {
+            ids[i] = properties[i];
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsString(itemID, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addInParamAsInt(properties.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(ids, true), JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        JIPointer[] itemIDs = (JIPointer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+
+        KeyedResultSet<Integer, String> results = new KeyedResultSet<Integer, String>();
+
+        for (int i = 0; i < properties.length; i++) {
+            results.add(new KeyedResult<Integer, String>(properties[i], ((JIString) itemIDs[i].getReferent()).getString(), errorCodes[i]));
+        }
+        return results;
+    }
+}

+ 161 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCServer.java

@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.common.impl.EnumString;
+import org.openscada.opc.dcom.common.impl.Helper;
+import org.openscada.opc.dcom.common.impl.OPCCommon;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.OPCENUMSCOPE;
+import org.openscada.opc.dcom.da.OPCSERVERSTATUS;
+
+import java.net.UnknownHostException;
+
+public class OPCServer extends BaseCOMObject {
+    public OPCServer(final IJIComObject opcServer) throws IllegalArgumentException, UnknownHostException, JIException {
+        super(opcServer.queryInterface(Constants.IOPCServer_IID));
+    }
+
+    /**
+     * Retrieve the current server status
+     *
+     * @return the current server status
+     * @throws JIException
+     */
+    public OPCSERVERSTATUS getStatus() throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(3);
+
+        callObject.addOutParamAsObject(new JIPointer(OPCSERVERSTATUS.getStruct()), JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        return OPCSERVERSTATUS.fromStruct((JIStruct) ((JIPointer) result[0]).getReferent());
+    }
+
+    public OPCGroupStateMgt addGroup(final String name, final boolean active, final int updateRate, final int clientHandle, final Integer timeBias, final Float percentDeadband, final int localeID) throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addInParamAsInt(active ? 1 : 0, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(updateRate, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(clientHandle, JIFlags.FLAG_NULL);
+        callObject.addInParamAsPointer(new JIPointer(timeBias), JIFlags.FLAG_NULL);
+        callObject.addInParamAsPointer(new JIPointer(percentDeadband), JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(localeID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(Integer.class, JIFlags.FLAG_NULL);
+        callObject.addInParamAsUUID(Constants.IOPCGroupStateMgt_IID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        return new OPCGroupStateMgt((IJIComObject) result[2]);
+    }
+
+    public void removeGroup(final int serverHandle, final boolean force) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(4);
+
+        callObject.addInParamAsInt(serverHandle, JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(force ? 1 : 0, JIFlags.FLAG_NULL);
+
+        getCOMObject().call(callObject);
+    }
+
+    public void removeGroup(final OPCGroupStateMgt group, final boolean force) throws JIException {
+        removeGroup(group.getState().getServerHandle(), force);
+    }
+
+    public OPCGroupStateMgt getGroupByName(final String name) throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsString(name, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addInParamAsUUID(Constants.IOPCGroupStateMgt_IID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object[] result = getCOMObject().call(callObject);
+
+        return new OPCGroupStateMgt((IJIComObject) result[0]);
+    }
+
+    /**
+     * Get the groups
+     *
+     * @param scope The scope to get
+     * @return A string enumerator with the groups
+     * @throws JIException
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     */
+    public EnumString getGroups(final OPCENUMSCOPE scope) throws JIException, IllegalArgumentException, UnknownHostException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(5);
+
+        callObject.addInParamAsShort((short) scope.id(), JIFlags.FLAG_NULL);
+        callObject.addInParamAsUUID(org.openscada.opc.dcom.common.Constants.IEnumString_IID, JIFlags.FLAG_NULL);
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        return new EnumString((IJIComObject) result[0]);
+    }
+
+    public OPCItemProperties getItemPropertiesService() {
+        try {
+            return new OPCItemProperties(getCOMObject());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public OPCItemIO getItemIOService() {
+        try {
+            return new OPCItemIO(getCOMObject());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Get the browser object (<code>IOPCBrowseServerAddressSpace</code>) from the server instance
+     *
+     * @return the browser object
+     */
+    public OPCBrowseServerAddressSpace getBrowser() {
+        try {
+            return new OPCBrowseServerAddressSpace(getCOMObject());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Get the common interface if supported
+     *
+     * @return the common interface or <code>null</code> if it is not supported
+     */
+    public OPCCommon getCommon() {
+        try {
+            return new OPCCommon(getCOMObject());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 92 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/da/impl/OPCSyncIO.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.da.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.KeyedResult;
+import org.openscada.opc.dcom.common.KeyedResultSet;
+import org.openscada.opc.dcom.common.Result;
+import org.openscada.opc.dcom.common.ResultSet;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.common.impl.Helper;
+import org.openscada.opc.dcom.da.Constants;
+import org.openscada.opc.dcom.da.OPCDATASOURCE;
+import org.openscada.opc.dcom.da.OPCITEMSTATE;
+import org.openscada.opc.dcom.da.WriteRequest;
+
+public class OPCSyncIO extends BaseCOMObject {
+    public OPCSyncIO(final IJIComObject opcSyncIO) throws JIException {
+        super(opcSyncIO.queryInterface(Constants.IOPCSyncIO_IID));
+    }
+
+    public KeyedResultSet<Integer, OPCITEMSTATE> read(final OPCDATASOURCE source, final Integer... serverHandles) throws JIException {
+        if (serverHandles == null || serverHandles.length == 0) {
+            return new KeyedResultSet<Integer, OPCITEMSTATE>();
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        callObject.addInParamAsShort((short) source.id(), JIFlags.FLAG_NULL);
+        callObject.addInParamAsInt(serverHandles.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(serverHandles, true), JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(OPCITEMSTATE.getStruct(), null, 1, true)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        KeyedResultSet<Integer, OPCITEMSTATE> results = new KeyedResultSet<Integer, OPCITEMSTATE>();
+        JIStruct[] states = (JIStruct[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+        Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[1]).getReferent()).getArrayInstance();
+
+        for (int i = 0; i < serverHandles.length; i++) {
+            results.add(new KeyedResult<Integer, OPCITEMSTATE>(serverHandles[i], OPCITEMSTATE.fromStruct(states[i]), errorCodes[i]));
+        }
+
+        return results;
+    }
+
+    public ResultSet<WriteRequest> write(final WriteRequest... requests) throws JIException {
+        if (requests.length == 0) {
+            return new ResultSet<WriteRequest>();
+        }
+
+        Integer[] items = new Integer[requests.length];
+        JIVariant[] values = new JIVariant[requests.length];
+        for (int i = 0; i < requests.length; i++) {
+            items[i] = requests[i].getServerHandle();
+            values[i] = Helper.fixVariant(requests[i].getValue());
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsInt(requests.length, JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(items, true), JIFlags.FLAG_NULL);
+        callObject.addInParamAsArray(new JIArray(values, true), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIArray(Integer.class, null, 1, true)), JIFlags.FLAG_NULL);
+
+        Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        Integer[] errorCodes = (Integer[]) ((JIArray) ((JIPointer) result[0]).getReferent()).getArrayInstance();
+
+        ResultSet<WriteRequest> results = new ResultSet<WriteRequest>();
+        for (int i = 0; i < requests.length; i++) {
+            results.add(new Result<WriteRequest>(requests[i], errorCodes[i]));
+        }
+        return results;
+    }
+}

+ 52 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/list/ClassDetails.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.list;
+
+/**
+ * Details about an OPC server class
+ *
+ * @author Jens Reimann &lt;jens.reimann@th4-systems.com&gt;
+ * @since 0.1.8
+ */
+public class ClassDetails {
+    private String _clsId;
+
+    private String _progId;
+
+    private String _description;
+
+    public String getClsId() {
+        return this._clsId;
+    }
+
+    public void setClsId(final String clsId) {
+        this._clsId = clsId;
+    }
+
+    public String getDescription() {
+        return this._description;
+    }
+
+    public void setDescription(final String description) {
+        this._description = description;
+    }
+
+    public String getProgId() {
+        return this._progId;
+    }
+
+    public void setProgId(final String progId) {
+        this._progId = progId;
+    }
+}

+ 20 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/list/Constants.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.list;
+
+public interface Constants extends org.openscada.opc.dcom.common.Constants {
+    public static final String IOPCServerList_IID = "13486D50-4821-11D2-A494-3CB306C10000";
+
+    public static final String OPCServerList_CLSID = "13486D51-4821-11D2-A494-3CB306C10000";
+}

+ 137 - 0
platform-opc/src/main/java/org/openscada/opc/dcom/list/impl/OPCServerList.java

@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.dcom.list.impl;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.*;
+import org.openscada.opc.dcom.common.impl.BaseCOMObject;
+import org.openscada.opc.dcom.common.impl.EnumGUID;
+import org.openscada.opc.dcom.common.impl.Helper;
+import org.openscada.opc.dcom.list.ClassDetails;
+import org.openscada.opc.dcom.list.Constants;
+import rpc.core.UUID;
+
+import java.net.UnknownHostException;
+
+/**
+ * This class implements the IOPCServerList (aka OPCEnum) service.
+ *
+ * @author Jens Reimann &lt;jens.reimann@th4-systems.com&gt;
+ */
+public class OPCServerList extends BaseCOMObject {
+    public OPCServerList(final IJIComObject listObject) throws JIException {
+        super(listObject.queryInterface(Constants.IOPCServerList_IID));
+    }
+
+    public JIClsid getCLSIDFromProgID(final String progId) throws JIException {
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(2);
+
+        callObject.addInParamAsString(progId, JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR);
+        callObject.addOutParamAsType(UUID.class, JIFlags.FLAG_NULL);
+
+        try {
+            Object[] result = getCOMObject().call(callObject);
+            return JIClsid.valueOf(((UUID) result[0]).toString());
+        } catch (JIException e) {
+            if (e.getErrorCode() == 0x800401F3) {
+                return null;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Return details about a serve class
+     *
+     * @param clsId A server class
+     * @throws JIException
+     */
+    public ClassDetails getClassDetails(final JIClsid clsId) throws JIException {
+        if (clsId == null) {
+            return null;
+        }
+
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(1);
+
+        callObject.addInParamAsUUID(clsId.getCLSID(), JIFlags.FLAG_NULL);
+
+        callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL);
+        callObject.addOutParamAsObject(new JIPointer(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR)), JIFlags.FLAG_NULL);
+
+        Object[] result = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        ClassDetails cd = new ClassDetails();
+        cd.setClsId(clsId.getCLSID());
+        cd.setProgId(((JIString) ((JIPointer) result[0]).getReferent()).getString());
+        cd.setDescription(((JIString) ((JIPointer) result[1]).getReferent()).getString());
+
+        return cd;
+    }
+
+    /*
+     HRESULT EnumClassesOfCategories(
+     [in]                       ULONG        cImplemented,
+     [in,size_is(cImplemented)] CATID        rgcatidImpl[],
+     [in]                       ULONG        cRequired,
+     [in,size_is(cRequired)]    CATID        rgcatidReq[],
+     [out]                      IEnumGUID ** ppenumClsid
+     );
+     */
+
+    public EnumGUID enumClassesOfCategories(final String[] implemented, final String[] required) throws IllegalArgumentException, UnknownHostException, JIException {
+        UUID[] u1 = new UUID[implemented.length];
+        UUID[] u2 = new UUID[required.length];
+
+        for (int i = 0; i < implemented.length; i++) {
+            u1[i] = new UUID(implemented[i]);
+        }
+
+        for (int i = 0; i < required.length; i++) {
+            u2[i] = new UUID(required[i]);
+        }
+
+        return enumClassesOfCategories(u1, u2);
+    }
+
+    public EnumGUID enumClassesOfCategories(final UUID[] implemented, final UUID[] required) throws IllegalArgumentException, UnknownHostException, JIException {
+        // ** CALL
+        JICallBuilder callObject = new JICallBuilder(true);
+        callObject.setOpnum(0);
+
+        // ** IN
+        callObject.addInParamAsInt(implemented.length, JIFlags.FLAG_NULL);
+        if (implemented.length == 0) {
+            callObject.addInParamAsPointer(new JIPointer(null), JIFlags.FLAG_NULL);
+        } else {
+            callObject.addInParamAsArray(new JIArray(implemented, true), JIFlags.FLAG_NULL);
+        }
+
+        callObject.addInParamAsInt(required.length, JIFlags.FLAG_NULL);
+        if (required.length == 0) {
+            callObject.addInParamAsPointer(new JIPointer(null), JIFlags.FLAG_NULL);
+        } else {
+            callObject.addInParamAsArray(new JIArray(required, true), JIFlags.FLAG_NULL);
+        }
+
+        // ** OUT
+        callObject.addOutParamAsType(IJIComObject.class, JIFlags.FLAG_NULL);
+
+        // ** RESULT
+        Object result[] = Helper.callRespectSFALSE(getCOMObject(), callObject);
+
+        return new EnumGUID((IJIComObject) result[0]);
+    }
+}

+ 20 - 0
platform-opc/src/main/java/org/openscada/opc/lib/common/AlreadyConnectedException.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.common;
+
+public class AlreadyConnectedException extends Exception {
+
+    private static final long serialVersionUID = -6494637563117314114L;
+
+}

+ 131 - 0
platform-opc/src/main/java/org/openscada/opc/lib/common/ConnectionInformation.java

@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.common;
+
+/**
+ * Holds the connection information
+ *
+ * @author Jens Reimann <jens.reimann@th4-systems.com>
+ * <p>
+ * If both <code>clsId</code> and <code>progId</code> are set then <code>clsId</code>
+ * has priority!
+ */
+public class ConnectionInformation {
+    private String _host = "localhost";
+
+    private String _domain = "localhost";
+
+    private String _user = "";
+
+    private String _password = "";
+
+    private String _clsid = null;
+
+    private String _progId = null;
+
+    public ConnectionInformation() {
+        super();
+    }
+
+    public ConnectionInformation(String host, String clsid, String user, String password) {
+        this._host = host;
+        this._clsid = clsid;
+        this._user = user;
+        this._password = password;
+    }
+
+    public ConnectionInformation(final String user, final String password) {
+        super();
+        this._user = user;
+        this._password = password;
+    }
+
+    public ConnectionInformation(final ConnectionInformation arg0) {
+        super();
+        this._user = arg0._user;
+        this._password = arg0._password;
+        this._domain = arg0._domain;
+        this._host = arg0._host;
+        this._progId = arg0._progId;
+        this._clsid = arg0._clsid;
+    }
+
+    public String getDomain() {
+        return this._domain;
+    }
+
+    /**
+     * Set the domain of the user used for logging on
+     *
+     * @param domain
+     */
+    public void setDomain(final String domain) {
+        this._domain = domain;
+    }
+
+    public String getHost() {
+        return this._host;
+    }
+
+    /**
+     * Set the host on which the server is located
+     *
+     * @param host The host to use, either an IP address oder hostname
+     */
+    public void setHost(final String host) {
+        this._host = host;
+    }
+
+    public String getPassword() {
+        return this._password;
+    }
+
+    public void setPassword(final String password) {
+        this._password = password;
+    }
+
+    public String getUser() {
+        return this._user;
+    }
+
+    public void setUser(final String user) {
+        this._user = user;
+    }
+
+    public String getClsid() {
+        return this._clsid;
+    }
+
+    public void setClsid(final String clsid) {
+        this._clsid = clsid;
+    }
+
+    public String getProgId() {
+        return this._progId;
+    }
+
+    public void setProgId(final String progId) {
+        this._progId = progId;
+    }
+
+    public String getClsOrProgId() {
+        if (this._clsid != null) {
+            return this._clsid;
+        } else if (this._progId != null) {
+            return this._progId;
+        } else {
+            return null;
+        }
+    }
+}

+ 20 - 0
platform-opc/src/main/java/org/openscada/opc/lib/common/NotConnectedException.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.common;
+
+public class NotConnectedException extends Exception {
+
+    private static final long serialVersionUID = -3745147771605524635L;
+
+}

+ 294 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/AccessBase.java

@@ -0,0 +1,294 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.openscada.opc.lib.common.NotConnectedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@Slf4j
+public abstract class AccessBase implements ServerConnectionStateListener {
+
+    protected Server server = null;
+
+    protected Group group = null;
+
+    protected boolean active = false;
+
+    private final List<AccessStateListener> stateListeners = new CopyOnWriteArrayList<AccessStateListener>();
+
+    private boolean bound = false;
+
+    /**
+     * Holds the item to callback assignment
+     */
+    protected Map<Item, DataCallback> items = new HashMap<Item, DataCallback>();
+
+    protected Map<String, Item> itemMap = new HashMap<String, Item>();
+
+    protected Map<Item, ItemState> itemCache = new HashMap<Item, ItemState>();
+
+    private int period = 0;
+
+    protected Map<String, DataCallback> itemSet = new HashMap<String, DataCallback>();
+
+    protected String logTag = null;
+
+    protected Logger dataLogger = null;
+
+    public AccessBase(final Server server, final int period) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException {
+        super();
+        this.server = server;
+        this.period = period;
+    }
+
+    public AccessBase(final Server server, final int period, final String logTag) {
+        super();
+        this.server = server;
+        this.period = period;
+        this.logTag = logTag;
+        if (this.logTag != null) {
+            this.dataLogger = LoggerFactory.getLogger("opc.data." + logTag);
+        }
+    }
+
+    public boolean isBound() {
+        return this.bound;
+    }
+
+    public synchronized void bind() {
+        if (isBound()) {
+            return;
+        }
+
+        this.server.addStateListener(this);
+        this.bound = true;
+    }
+
+    public synchronized void unbind() throws JIException {
+        if (!isBound()) {
+            return;
+        }
+
+        this.server.removeStateListener(this);
+        this.bound = false;
+
+        stop();
+    }
+
+    public boolean isActive() {
+        return this.active;
+    }
+
+    public void addStateListener(final AccessStateListener listener) {
+        this.stateListeners.add(listener);
+        listener.stateChanged(isActive());
+    }
+
+    public void removeStateListener(final AccessStateListener listener) {
+        this.stateListeners.remove(listener);
+    }
+
+    protected void notifyStateListenersState(final boolean state) {
+
+        final List<AccessStateListener> list = new ArrayList<AccessStateListener>(this.stateListeners);
+
+        for (final AccessStateListener listener : list) {
+            listener.stateChanged(state);
+        }
+    }
+
+    protected void notifyStateListenersError(final Throwable t) {
+        final List<AccessStateListener> list = new ArrayList<AccessStateListener>(this.stateListeners);
+
+        for (final AccessStateListener listener : list) {
+            listener.errorOccured(t);
+        }
+    }
+
+    public int getPeriod() {
+        return this.period;
+    }
+
+    public synchronized void addItem(final String itemId, final DataCallback dataCallback) throws JIException, AddFailedException {
+
+        if (this.itemSet.containsKey(itemId)) {
+            return;
+        }
+
+        this.itemSet.put(itemId, dataCallback);
+
+        if (isActive()) {
+            realizeItem(itemId);
+        }
+    }
+
+    public synchronized void removeItem(final String itemId) {
+
+        if (!this.itemSet.containsKey(itemId)) {
+            return;
+        }
+
+        this.itemSet.remove(itemId);
+
+        if (isActive()) {
+            unrealizeItem(itemId);
+        }
+    }
+
+    public void connectionStateChanged(final boolean connected) {
+        try {
+            if (connected) {
+                start();
+            } else {
+                stop();
+            }
+        } catch (final Exception e) {
+            log.error(String.format("Failed to change state (%s)", connected), e);
+        }
+    }
+
+    protected synchronized void start() throws JIException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException {
+        if (isActive()) {
+            return;
+        }
+
+        log.debug("Create a new group");
+        this.group = this.server.addGroup();
+        this.group.setActive(true);
+        this.active = true;
+
+        notifyStateListenersState(true);
+
+        realizeAll();
+    }
+
+    protected void realizeItem(final String itemId) throws JIException, AddFailedException {
+
+        log.debug("Realizing item: {}", itemId);
+
+        final DataCallback dataCallback = this.itemSet.get(itemId);
+        if (dataCallback == null) {
+            return;
+        }
+
+        final Item item = this.group.addItem(itemId);
+        this.items.put(item, dataCallback);
+        this.itemMap.put(itemId, item);
+    }
+
+    protected void unrealizeItem(final String itemId) {
+        final Item item = this.itemMap.remove(itemId);
+        this.items.remove(item);
+        this.itemCache.remove(item);
+
+        try {
+            this.group.removeItem(itemId);
+        } catch (final Throwable e) {
+            log.error(String.format("Failed to unrealize item '%s'", itemId), e);
+        }
+    }
+
+    /**
+     * FIXME: need some perfomance boost: subscribe all in one call
+     */
+    protected void realizeAll() {
+        for (final String itemId : this.itemSet.keySet()) {
+            try {
+                realizeItem(itemId);
+            } catch (final AddFailedException e) {
+                Integer rc = e.getErrors().get(itemId);
+                if (rc == null) {
+                    rc = -1;
+                }
+                log.warn(String.format("Failed to add item: %s (%08X)", itemId, rc));
+
+            } catch (final Exception e) {
+                log.warn("Failed to realize item: " + itemId, e);
+            }
+        }
+    }
+
+    protected void unrealizeAll() {
+        this.items.clear();
+        this.itemCache.clear();
+        try {
+            this.group.clear();
+        } catch (final JIException e) {
+            log.info("Failed to clear group. No problem if we already lost the connection", e);
+        }
+    }
+
+    protected synchronized void stop() throws JIException {
+        if (!isActive()) {
+            return;
+        }
+
+        unrealizeAll();
+
+        this.active = false;
+        notifyStateListenersState(false);
+
+        try {
+            this.group.remove();
+        } catch (final Throwable t) {
+            log.warn("Failed to disable group. No problem if we already lost connection");
+        }
+        this.group = null;
+    }
+
+    public synchronized void clear() {
+        this.itemSet.clear();
+        this.items.clear();
+        this.itemMap.clear();
+        this.itemCache.clear();
+    }
+
+    protected void updateItem(final Item item, final ItemState itemState) {
+        if (this.dataLogger != null) {
+            this.dataLogger.debug("Update item: {}, {}", item.getId(), itemState);
+        }
+
+        final DataCallback dataCallback = this.items.get(item);
+        if (dataCallback == null) {
+            return;
+        }
+
+        final ItemState cachedState = this.itemCache.get(item);
+        if (cachedState == null) {
+            this.itemCache.put(item, itemState);
+            dataCallback.changed(item, itemState);
+        } else {
+            if (!cachedState.equals(itemState)) {
+                this.itemCache.put(item, itemState);
+                dataCallback.changed(item, itemState);
+            }
+        }
+    }
+
+    protected void handleError(final Throwable e) {
+        notifyStateListenersError(e);
+        this.server.dispose();
+    }
+
+}

+ 20 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/AccessStateListener.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+public interface AccessStateListener {
+    public abstract void stateChanged(boolean state);
+
+    public abstract void errorOccured(Throwable t);
+}

+ 53 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/AddFailedException.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AddFailedException extends Exception {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 5299486640366935298L;
+
+    private Map<String, Integer> _errors = new HashMap<String, Integer>();
+
+    private Map<String, Item> _items = new HashMap<String, Item>();
+
+    public AddFailedException(final Map<String, Integer> errors, final Map<String, Item> items) {
+        super();
+        this._errors = errors;
+        this._items = items;
+    }
+
+    /**
+     * Get the map of item id to error code
+     *
+     * @return the result map containing the failed items
+     */
+    public Map<String, Integer> getErrors() {
+        return this._errors;
+    }
+
+    /**
+     * Get the map of item it to item object
+     *
+     * @return the result map containing the succeeded items
+     */
+    public Map<String, Item> getItems() {
+        return this._items;
+    }
+}

+ 110 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/Async20Access.java

@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.openscada.opc.dcom.common.EventHandler;
+import org.openscada.opc.dcom.common.KeyedResult;
+import org.openscada.opc.dcom.common.KeyedResultSet;
+import org.openscada.opc.dcom.common.ResultSet;
+import org.openscada.opc.dcom.da.IOPCDataCallback;
+import org.openscada.opc.dcom.da.OPCDATASOURCE;
+import org.openscada.opc.dcom.da.ValueData;
+import org.openscada.opc.dcom.da.impl.OPCAsyncIO2;
+import org.openscada.opc.lib.common.NotConnectedException;
+
+import java.net.UnknownHostException;
+
+@Slf4j
+public class Async20Access extends AccessBase implements IOPCDataCallback {
+
+    private EventHandler eventHandler = null;
+
+    private boolean initialRefresh = false;
+
+    public Async20Access(final Server server, final int period, final boolean initialRefresh) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException {
+        super(server, period);
+        this.initialRefresh = initialRefresh;
+    }
+
+    public Async20Access(final Server server, final int period, final boolean initialRefresh, final String logTag) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException {
+        super(server, period, logTag);
+        this.initialRefresh = initialRefresh;
+    }
+
+    @Override
+    protected synchronized void start() throws JIException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException {
+        if (isActive()) {
+            return;
+        }
+
+        super.start();
+
+        this.eventHandler = this.group.attach(this);
+        if (!this.items.isEmpty() && this.initialRefresh) {
+            final OPCAsyncIO2 async20 = this.group.getAsyncIO20();
+            if (async20 == null) {
+                throw new NotConnectedException();
+            }
+
+            this.group.getAsyncIO20().refresh(OPCDATASOURCE.OPC_DS_CACHE, 0);
+        }
+    }
+
+    @Override
+    protected synchronized void stop() throws JIException {
+        if (!isActive()) {
+            return;
+        }
+
+        if (this.eventHandler != null) {
+            try {
+                this.eventHandler.detach();
+            } catch (final Throwable e) {
+                log.warn("Failed to detach group", e);
+            }
+
+            this.eventHandler = null;
+        }
+
+        super.stop();
+    }
+
+    public void cancelComplete(final int transactionId, final int serverGroupHandle) {
+    }
+
+    public void dataChange(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final KeyedResultSet<Integer, ValueData> result) {
+        log.debug("dataChange - transId {}, items: {}", transactionId, result.size());
+
+        final Group group = this.group;
+        if (group == null) {
+            return;
+        }
+
+        for (final KeyedResult<Integer, ValueData> entry : result) {
+            final Item item = group.findItemByClientHandle(entry.getKey());
+            log.debug("Update for '{}'", item.getId());
+            updateItem(item, new ItemState(entry.getErrorCode(), entry.getValue().getValue(), entry.getValue().getTimestamp(), entry.getValue().getQuality()));
+        }
+    }
+
+    public void readComplete(final int transactionId, final int serverGroupHandle, final int masterQuality, final int masterErrorCode, final KeyedResultSet<Integer, ValueData> result) {
+        log.debug("readComplete - transId {}", transactionId);
+    }
+
+    public void writeComplete(final int transactionId, final int serverGroupHandle, final int masterErrorCode, final ResultSet<Integer> result) {
+        log.debug("writeComplete - transId {}", transactionId);
+    }
+}

+ 183 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/AutoReconnectController.java

@@ -0,0 +1,183 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+@Slf4j
+public class AutoReconnectController implements ServerConnectionStateListener {
+
+    private static final int DEFAULT_DELAY = 5 * 1000;
+
+    private int _delay;
+
+    private final Server _server;
+
+    private final Set<AutoReconnectListener> _listeners = new CopyOnWriteArraySet<AutoReconnectListener>();
+
+    private AutoReconnectState _state = AutoReconnectState.DISABLED;
+
+    private Thread _connectTask = null;
+
+    public AutoReconnectController(final Server server) {
+        this(server, DEFAULT_DELAY);
+    }
+
+    public AutoReconnectController(final Server server, final int delay) {
+        super();
+        setDelay(delay);
+
+        this._server = server;
+        this._server.addStateListener(this);
+    }
+
+    public void addListener(final AutoReconnectListener listener) {
+        if (listener != null) {
+            this._listeners.add(listener);
+            listener.stateChanged(this._state);
+        }
+    }
+
+    public void removeListener(final AutoReconnectListener listener) {
+        this._listeners.remove(listener);
+    }
+
+    protected void notifyStateChange(final AutoReconnectState state) {
+        this._state = state;
+        for (AutoReconnectListener listener : this._listeners) {
+            listener.stateChanged(state);
+        }
+    }
+
+    public int getDelay() {
+        return this._delay;
+    }
+
+    /**
+     * Set the reconnect delay. If the delay less than or equal to zero it will be
+     * the default delay time.
+     *
+     * @param delay The delay to use
+     */
+    public void setDelay(int delay) {
+        if (delay <= 0) {
+            delay = DEFAULT_DELAY;
+        }
+        this._delay = delay;
+    }
+
+    public synchronized void connect() {
+        if (isRequested()) {
+            return;
+        }
+
+        log.debug("Requesting connection");
+        notifyStateChange(AutoReconnectState.DISCONNECTED);
+
+        triggerReconnect(false);
+    }
+
+    public synchronized void disconnect() {
+        if (!isRequested()) {
+            return;
+        }
+
+        log.debug("Un-Requesting connection");
+
+        notifyStateChange(AutoReconnectState.DISABLED);
+        this._server.disconnect();
+    }
+
+    public boolean isRequested() {
+        return this._state != AutoReconnectState.DISABLED;
+    }
+
+    public synchronized void connectionStateChanged(final boolean connected) {
+        log.debug("Connection state changed: " + connected);
+
+        if (!connected) {
+            if (isRequested()) {
+                notifyStateChange(AutoReconnectState.DISCONNECTED);
+                triggerReconnect(true);
+            }
+        } else {
+            if (!isRequested()) {
+                this._server.disconnect();
+            } else {
+                notifyStateChange(AutoReconnectState.CONNECTED);
+            }
+        }
+    }
+
+    private synchronized void triggerReconnect(final boolean wait) {
+        if (this._connectTask != null) {
+            log.info("Connect thread already running");
+            return;
+        }
+
+        log.debug("Trigger reconnect");
+
+        this._connectTask = new Thread(new Runnable() {
+
+            public void run() {
+                boolean result = false;
+                try {
+                    result = performReconnect(wait);
+                } finally {
+                    AutoReconnectController.this._connectTask = null;
+                    log.debug(String.format("performReconnect completed : %s", result));
+                    if (!result) {
+                        triggerReconnect(true);
+                    }
+                }
+            }
+        }, "OPCReconnectThread");
+        this._connectTask.setDaemon(true);
+        this._connectTask.start();
+    }
+
+    private boolean performReconnect(final boolean wait) {
+        try {
+            if (wait) {
+                notifyStateChange(AutoReconnectState.WAITING);
+                log.debug(String.format("Delaying (%s)...", this._delay));
+                Thread.sleep(this._delay);
+            }
+        } catch (InterruptedException e) {
+        }
+
+        if (!isRequested()) {
+            log.debug("Request canceled during delay");
+            return true;
+        }
+
+        try {
+            log.debug("Connecting to server");
+            notifyStateChange(AutoReconnectState.CONNECTING);
+            synchronized (this) {
+                this._server.connect();
+                return true;
+            }
+            // CONNECTED state will be set by server callback
+        } catch (Throwable e) {
+            log.info("Re-connect failed", e);
+            notifyStateChange(AutoReconnectState.DISCONNECTED);
+            return false;
+        }
+    }
+
+}

+ 18 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/AutoReconnectListener.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+public interface AutoReconnectListener {
+    public abstract void stateChanged(AutoReconnectState state);
+}

+ 44 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/AutoReconnectState.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+/**
+ * A state for the auto-reconnect controller
+ *
+ * @author Jens Reimann
+ */
+public enum AutoReconnectState {
+    /**
+     * Auto reconnect is disabled.
+     */
+    DISABLED,
+    /**
+     * Auto reconnect is enabled, but the connection is currently not established.
+     */
+    DISCONNECTED,
+    /**
+     * Auto reconnect is enabled, the connection is not established and the controller
+     * is currently waiting the delay until it will reconnect.
+     */
+    WAITING,
+    /**
+     * Auto reconnect is enabled, the connection is not established but the controller
+     * currently tries to establish the connection.
+     */
+    CONNECTING,
+    /**
+     * Auto reconnect is enabled and the connection is established.
+     */
+    CONNECTED
+}

+ 18 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/DataCallback.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+public interface DataCallback {
+    void changed(Item item, ItemState itemState);
+}

+ 23 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/DuplicateGroupException.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+public class DuplicateGroupException extends Exception {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 826553520690295478L;
+
+}

+ 66 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/ErrorMessageResolver.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.openscada.opc.dcom.common.impl.OPCCommon;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An error message resolver that will lookup the error code using the
+ * server interface and will cache the result locally.
+ *
+ * @author Jens Reimann
+ */
+@Slf4j
+public class ErrorMessageResolver {
+
+    private OPCCommon _opcCommon = null;
+
+    private final Map<Integer, String> _messageCache = new HashMap<Integer, String>();
+
+    private int _localeId = 0;
+
+    public ErrorMessageResolver(final OPCCommon opcCommon, final int localeId) {
+        super();
+        this._opcCommon = opcCommon;
+        this._localeId = localeId;
+    }
+
+    /**
+     * Get an error message from an error code
+     *
+     * @param errorCode The error code to look up
+     * @return the error message or <code>null</code> if no message could be looked up
+     */
+    public synchronized String getMessage(final int errorCode) {
+        String message = this._messageCache.get(Integer.valueOf(errorCode));
+
+        if (message == null) {
+            try {
+                message = this._opcCommon.getErrorString(errorCode, this._localeId);
+                log.info(String.format("Resolved %08X to '%s'", errorCode, message));
+            } catch (JIException e) {
+                log.warn(String.format("Failed to resolve error code for %08X", errorCode), e);
+            }
+            if (message != null) {
+                this._messageCache.put(errorCode, message);
+            }
+        }
+        return message;
+    }
+}

+ 362 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/Group.java

@@ -0,0 +1,362 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.openscada.opc.dcom.common.*;
+import org.openscada.opc.dcom.da.*;
+import org.openscada.opc.dcom.da.impl.OPCAsyncIO2;
+import org.openscada.opc.dcom.da.impl.OPCGroupStateMgt;
+import org.openscada.opc.dcom.da.impl.OPCItemMgt;
+import org.openscada.opc.dcom.da.impl.OPCSyncIO;
+
+import java.net.UnknownHostException;
+import java.util.*;
+
+@Slf4j
+public class Group {
+
+    private static Random _random = new Random();
+
+    private Server _server = null;
+
+    private final int _serverHandle;
+
+    private OPCGroupStateMgt _group = null;
+
+    private OPCItemMgt _items = null;
+
+    private OPCSyncIO _syncIO = null;
+
+    private final Map<String, Integer> _itemHandleMap = new HashMap<String, Integer>();
+
+    private final Map<Integer, Item> _itemMap = new HashMap<Integer, Item>();
+
+    private final Map<Integer, Item> _itemClientMap = new HashMap<Integer, Item>();
+
+    Group(final Server server, final int serverHandle, final OPCGroupStateMgt group) throws IllegalArgumentException, UnknownHostException, JIException {
+        log.debug("Creating new group instance with COM group " + group);
+        this._server = server;
+        this._serverHandle = serverHandle;
+        this._group = group;
+        this._items = group.getItemManagement();
+        this._syncIO = group.getSyncIO();
+    }
+
+    public void setActive(final boolean state) throws JIException {
+        this._group.setState(null, state, null, null, null, null);
+    }
+
+    /**
+     * remove the group from the server
+     *
+     * @throws JIException
+     */
+    public void remove() throws JIException {
+        this._server.removeGroup(this, true);
+    }
+
+    public boolean isActive() throws JIException {
+        return this._group.getState().isActive();
+    }
+
+    /**
+     * Get the group name from the server
+     *
+     * @return The group name fetched from the server
+     * @throws JIException
+     */
+    public String getName() throws JIException {
+        return this._group.getState().getName();
+    }
+
+    /**
+     * Change the group name
+     *
+     * @param name the new name of the group
+     * @throws JIException
+     */
+    public void setName(final String name) throws JIException {
+        this._group.setName(name);
+    }
+
+    /**
+     * Add a single item. Actually calls {@link #addItems(String[])} with only
+     * one paraemter
+     *
+     * @param item The item to add
+     * @return The added item
+     * @throws JIException        The add operation failed
+     * @throws AddFailedException The item was not added due to an error
+     */
+    public Item addItem(final String item) throws JIException, AddFailedException {
+        Map<String, Item> items = addItems(item);
+        return items.get(item);
+    }
+
+    /**
+     * Validate item ids and get additional information to them.
+     * <br>
+     * According to the OPC specification you should first <em>validate</em>
+     * the items and the <em>add</em> them. The spec also says that when a server
+     * lets the item pass validation it must also let them pass the add operation.
+     *
+     * @param items The items to validate
+     * @return A result map of item id to result information (including error code).
+     * @throws JIException
+     */
+    public synchronized Map<String, Result<OPCITEMRESULT>> validateItems(final String... items) throws JIException {
+        OPCITEMDEF[] defs = new OPCITEMDEF[items.length];
+        for (int i = 0; i < items.length; i++) {
+            defs[i] = new OPCITEMDEF();
+            defs[i].setItemID(items[i]);
+        }
+
+        KeyedResultSet<OPCITEMDEF, OPCITEMRESULT> result = this._items.validate(defs);
+
+        Map<String, Result<OPCITEMRESULT>> resultMap = new HashMap<String, Result<OPCITEMRESULT>>();
+        for (KeyedResult<OPCITEMDEF, OPCITEMRESULT> resultEntry : result) {
+            resultMap.put(resultEntry.getKey().getItemID(), new Result<OPCITEMRESULT>(resultEntry.getValue(), resultEntry.getErrorCode()));
+        }
+
+        return resultMap;
+    }
+
+    /**
+     * Add new items to the group
+     *
+     * @param items The items (by string id) to add
+     * @return A result map of id to item object
+     * @throws JIException        The add operation completely failed. No item was added.
+     * @throws AddFailedException If one or more item could not be added. Item without error where added.
+     */
+    public synchronized Map<String, Item> addItems(final String... items) throws JIException, AddFailedException {
+        // Find which items we already have
+        Map<String, Integer> handles = findItems(items);
+
+        List<Integer> foundItems = new ArrayList<Integer>(items.length);
+        List<String> missingItems = new ArrayList<String>();
+
+        // separate missing items from the found ones
+        for (Map.Entry<String, Integer> entry : handles.entrySet()) {
+            if (entry.getValue() == null) {
+                missingItems.add(entry.getKey());
+            } else {
+                foundItems.add(entry.getValue());
+            }
+        }
+
+        // now fetch missing items from OPC server
+        Set<Integer> newClientHandles = new HashSet<Integer>();
+        OPCITEMDEF[] itemDef = new OPCITEMDEF[missingItems.size()];
+        for (int i = 0; i < missingItems.size(); i++) {
+            OPCITEMDEF def = new OPCITEMDEF();
+            def.setItemID(missingItems.get(i));
+            def.setActive(true);
+
+            Integer clientHandle;
+            do {
+                clientHandle = _random.nextInt();
+            } while (this._itemClientMap.containsKey(clientHandle) || newClientHandles.contains(clientHandle));
+            newClientHandles.add(clientHandle);
+            def.setClientHandle(clientHandle);
+
+            itemDef[i] = def;
+        }
+
+        // check the result and add new items
+        Map<String, Integer> failedItems = new HashMap<String, Integer>();
+        KeyedResultSet<OPCITEMDEF, OPCITEMRESULT> result = this._items.add(itemDef);
+        int i = 0;
+        for (KeyedResult<OPCITEMDEF, OPCITEMRESULT> entry : result) {
+            if (entry.getErrorCode() == 0) {
+                Item item = new Item(this, entry.getValue().getServerHandle(), itemDef[i].getClientHandle(), entry.getKey().getItemID());
+                addItem(item);
+                foundItems.add(item.getServerHandle());
+            } else {
+                failedItems.put(entry.getKey().getItemID(), entry.getErrorCode());
+            }
+            i++;
+        }
+
+        // if we have failed items then throw an exception with the result
+        if (failedItems.size() != 0) {
+            throw new AddFailedException(failedItems, findItems(foundItems));
+        }
+
+        // simply return the result in case of success
+        return findItems(foundItems);
+    }
+
+    private synchronized void addItem(final Item item) {
+        log.debug(String.format("Adding item: '%s', %d", item.getId(), item.getServerHandle()));
+
+        this._itemHandleMap.put(item.getId(), item.getServerHandle());
+        this._itemMap.put(item.getServerHandle(), item);
+        this._itemClientMap.put(item.getClientHandle(), item);
+    }
+
+    private synchronized void removeItem(final Item item) {
+        this._itemHandleMap.remove(item.getId());
+        this._itemMap.remove(item.getServerHandle());
+        this._itemClientMap.remove(item.getClientHandle());
+    }
+
+    protected Item getItemByOPCItemId(final String opcItemId) {
+        Integer serverHandle = this._itemHandleMap.get(opcItemId);
+        if (serverHandle == null) {
+            log.debug(String.format("Failed to locate item with id '%s'", opcItemId));
+            return null;
+        }
+        log.debug(String.format("Item '%s' has server id '%d'", opcItemId, serverHandle));
+        return this._itemMap.get(serverHandle);
+    }
+
+    private synchronized Map<String, Integer> findItems(final String[] items) {
+        Map<String, Integer> data = new HashMap<String, Integer>();
+
+        for (int i = 0; i < items.length; i++) {
+            data.put(items[i], this._itemHandleMap.get(items[i]));
+        }
+
+        return data;
+    }
+
+    private synchronized Map<String, Item> findItems(final Collection<Integer> handles) {
+        Map<String, Item> itemMap = new HashMap<String, Item>();
+        for (Integer i : handles) {
+            Item item = this._itemMap.get(i);
+            if (item != null) {
+                itemMap.put(item.getId(), item);
+            }
+        }
+        return itemMap;
+    }
+
+    protected void checkItems(final Item[] items) {
+        for (Item item : items) {
+            if (item.getGroup() != this) {
+                throw new IllegalArgumentException("Item does not belong to this group");
+            }
+        }
+    }
+
+    public void setActive(final boolean state, final Item... items) throws JIException {
+        checkItems(items);
+
+        Integer[] handles = new Integer[items.length];
+        for (int i = 0; i < items.length; i++) {
+            handles[i] = items[i].getServerHandle();
+        }
+
+        this._items.setActiveState(state, handles);
+    }
+
+    protected Integer[] getServerHandles(final Item[] items) {
+        checkItems(items);
+
+        Integer[] handles = new Integer[items.length];
+
+        for (int i = 0; i < items.length; i++) {
+            handles[i] = items[i].getServerHandle();
+        }
+
+        return handles;
+    }
+
+    public synchronized Map<Item, Integer> write(final WriteRequest... requests) throws JIException {
+        Item[] items = new Item[requests.length];
+
+        for (int i = 0; i < requests.length; i++) {
+            items[i] = requests[i].getItem();
+        }
+
+        Integer[] handles = getServerHandles(items);
+
+        org.openscada.opc.dcom.da.WriteRequest[] wr = new org.openscada.opc.dcom.da.WriteRequest[items.length];
+        for (int i = 0; i < items.length; i++) {
+            wr[i] = new org.openscada.opc.dcom.da.WriteRequest(handles[i], requests[i].getValue());
+        }
+
+        ResultSet<org.openscada.opc.dcom.da.WriteRequest> resultSet = this._syncIO.write(wr);
+
+        Map<Item, Integer> result = new HashMap<Item, Integer>();
+        for (int i = 0; i < requests.length; i++) {
+            Result<org.openscada.opc.dcom.da.WriteRequest> entry = resultSet.get(i);
+            result.put(requests[i].getItem(), entry.getErrorCode());
+        }
+
+        return result;
+    }
+
+    public synchronized Map<Item, ItemState> read(final boolean device, final Item... items) throws JIException {
+        Integer[] handles = getServerHandles(items);
+
+        KeyedResultSet<Integer, OPCITEMSTATE> states = this._syncIO.read(device ? OPCDATASOURCE.OPC_DS_DEVICE : OPCDATASOURCE.OPC_DS_CACHE, handles);
+
+        Map<Item, ItemState> data = new HashMap<Item, ItemState>();
+        for (KeyedResult<Integer, OPCITEMSTATE> entry : states) {
+            Item item = this._itemMap.get(entry.getKey());
+            ItemState state = new ItemState(entry.getErrorCode(), entry.getValue().getValue(), entry.getValue().getTimestamp().asCalendar(), entry.getValue().getQuality());
+            data.put(item, state);
+        }
+        return data;
+    }
+
+    public Server getServer() {
+        return this._server;
+    }
+
+    public synchronized void clear() throws JIException {
+        Integer[] handles = this._itemMap.keySet().toArray(new Integer[0]);
+        try {
+            this._items.remove(handles);
+        } finally {
+            // in any case clear our maps
+            this._itemHandleMap.clear();
+            this._itemMap.clear();
+            this._itemClientMap.clear();
+        }
+    }
+
+    public synchronized OPCAsyncIO2 getAsyncIO20() {
+        return this._group.getAsyncIO2();
+    }
+
+    public synchronized EventHandler attach(final IOPCDataCallback dataCallback) throws JIException {
+        return this._group.attach(dataCallback);
+    }
+
+    public Item findItemByClientHandle(final int clientHandle) {
+        return this._itemClientMap.get(clientHandle);
+    }
+
+    public int getServerHandle() {
+        return this._serverHandle;
+    }
+
+    public synchronized void removeItem(final String opcItemId) throws IllegalArgumentException, UnknownHostException, JIException {
+        log.debug(String.format("Removing item '%s'", opcItemId));
+        Item item = getItemByOPCItemId(opcItemId);
+        if (item != null) {
+            this._group.getItemManagement().remove(item.getServerHandle());
+            removeItem(item);
+            log.debug(String.format("Removed item '%s'", opcItemId));
+        } else {
+            log.warn(String.format("Unable to find item '%s'", opcItemId));
+        }
+    }
+
+}

+ 67 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/Item.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIVariant;
+
+@Slf4j
+public class Item {
+
+    private Group _group = null;
+
+    private int _serverHandle = 0;
+
+    private int _clientHandle = 0;
+
+    private String _id = null;
+
+    Item(final Group group, final int serverHandle, final int clientHandle, final String id) {
+        super();
+        log.debug(String.format("Adding new item '%s' (0x%08X) for group %s", id, serverHandle, group.toString()));
+        this._group = group;
+        this._serverHandle = serverHandle;
+        this._clientHandle = clientHandle;
+        this._id = id;
+    }
+
+    public Group getGroup() {
+        return this._group;
+    }
+
+    public int getServerHandle() {
+        return this._serverHandle;
+    }
+
+    public int getClientHandle() {
+        return this._clientHandle;
+    }
+
+    public String getId() {
+        return this._id;
+    }
+
+    public void setActive(final boolean state) throws JIException {
+        this._group.setActive(state, this);
+    }
+
+    public ItemState read(final boolean device) throws JIException {
+        return this._group.read(device, this).get(this);
+    }
+
+    public Integer write(final JIVariant value) throws JIException {
+        return this._group.write(new WriteRequest[]{new WriteRequest(this, value)}).get(this);
+    }
+}

+ 127 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/ItemState.java

@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import org.jinterop.dcom.core.JIVariant;
+
+import java.util.Calendar;
+
+public class ItemState {
+    private int _errorCode = 0;
+
+    private JIVariant _value = null;
+
+    private Calendar _timestamp = null;
+
+    private Short _quality = null;
+
+    public ItemState(final int errorCode, final JIVariant value, final Calendar timestamp, final Short quality) {
+        super();
+        this._errorCode = errorCode;
+        this._value = value;
+        this._timestamp = timestamp;
+        this._quality = quality;
+    }
+
+    public ItemState() {
+        super();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Value: %s, Timestamp: %tc, Quality: %s, ErrorCode: %08x", this._value, this._timestamp, this._quality, this._errorCode);
+    }
+
+    public Short getQuality() {
+        return this._quality;
+    }
+
+    public void setQuality(final Short quality) {
+        this._quality = quality;
+    }
+
+    public Calendar getTimestamp() {
+        return this._timestamp;
+    }
+
+    public void setTimestamp(final Calendar timestamp) {
+        this._timestamp = timestamp;
+    }
+
+    public JIVariant getValue() {
+        return this._value;
+    }
+
+    public void setValue(final JIVariant value) {
+        this._value = value;
+    }
+
+    public int getErrorCode() {
+        return this._errorCode;
+    }
+
+    public void setErrorCode(final int errorCode) {
+        this._errorCode = errorCode;
+    }
+
+    @Override
+    public int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + this._errorCode;
+        result = PRIME * result + (this._quality == null ? 0 : this._quality.hashCode());
+        result = PRIME * result + (this._timestamp == null ? 0 : this._timestamp.hashCode());
+        result = PRIME * result + (this._value == null ? 0 : this._value.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ItemState other = (ItemState) obj;
+        if (this._errorCode != other._errorCode) {
+            return false;
+        }
+        if (this._quality == null) {
+            if (other._quality != null) {
+                return false;
+            }
+        } else if (!this._quality.equals(other._quality)) {
+            return false;
+        }
+        if (this._timestamp == null) {
+            if (other._timestamp != null) {
+                return false;
+            }
+        } else if (!this._timestamp.equals(other._timestamp)) {
+            return false;
+        }
+        if (this._value == null) {
+            if (other._value != null) {
+                return false;
+            }
+        } else if (!this._value.equals(other._value)) {
+            return false;
+        }
+        return true;
+    }
+}

+ 432 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/Server.java

@@ -0,0 +1,432 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIClsid;
+import org.jinterop.dcom.core.JIComServer;
+import org.jinterop.dcom.core.JIProgId;
+import org.jinterop.dcom.core.JISession;
+import org.openscada.opc.dcom.da.OPCNAMESPACETYPE;
+import org.openscada.opc.dcom.da.OPCSERVERSTATUS;
+import org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace;
+import org.openscada.opc.dcom.da.impl.OPCGroupStateMgt;
+import org.openscada.opc.dcom.da.impl.OPCServer;
+import org.openscada.opc.lib.common.AlreadyConnectedException;
+import org.openscada.opc.lib.common.ConnectionInformation;
+import org.openscada.opc.lib.common.NotConnectedException;
+import org.openscada.opc.lib.da.browser.FlatBrowser;
+import org.openscada.opc.lib.da.browser.TreeBrowser;
+import org.openscada.opc.lib.list.ServerList;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+
+@Slf4j
+public class Server {
+
+    private final ConnectionInformation connectionInformation;
+
+    private JISession session;
+
+    private JIComServer comServer;
+
+    private OPCServer server;
+
+    private boolean defaultActive = true;
+
+    private int defaultUpdateRate = 1000;
+
+    private Integer defaultTimeBias;
+
+    private Float defaultPercentDeadband;
+
+    private int defaultLocaleID = 0;
+
+    private ErrorMessageResolver errorMessageResolver;
+
+    private final Map<Integer, Group> groups = new HashMap<>(16);
+
+    private final List<ServerConnectionStateListener> stateListeners = new CopyOnWriteArrayList<>();
+
+    private final ScheduledExecutorService scheduler;
+
+    public Server(final ConnectionInformation connectionInformation,
+                  final ScheduledExecutorService scheduler) {
+        super();
+        this.connectionInformation = connectionInformation;
+        this.scheduler = scheduler;
+    }
+
+    /**
+     * Gets the scheduler for the server. Note that this scheduler might get
+     * blocked for a short time if the connection breaks. It should not be used
+     * for time critical operations.
+     *
+     * @return the scheduler for the server
+     */
+    public ScheduledExecutorService getScheduler() {
+        return this.scheduler;
+    }
+
+    protected synchronized boolean isConnected() {
+        return this.session != null;
+    }
+
+    public synchronized void connect() throws IllegalArgumentException, UnknownHostException, JIException, AlreadyConnectedException {
+        if (isConnected()) {
+            throw new AlreadyConnectedException();
+        }
+
+        final int socketTimeout = Integer.getInteger("rpc.socketTimeout", 0);
+        log.debug(String.format("Socket timeout: %s ", socketTimeout));
+
+        try {
+            if (this.connectionInformation.getClsid() != null) {
+                this.session = JISession.createSession(
+                        this.connectionInformation.getDomain(),
+                        this.connectionInformation.getUser(),
+                        this.connectionInformation.getPassword());
+                this.session.setGlobalSocketTimeout(socketTimeout);
+                this.session.useSessionSecurity(true);
+                this.comServer = new JIComServer(
+                        JIClsid.valueOf(this.connectionInformation.getClsid()),
+                        this.connectionInformation.getHost(), this.session);
+            } else if (this.connectionInformation.getProgId() != null) {
+                this.session = JISession.createSession(
+                        this.connectionInformation.getDomain(),
+                        this.connectionInformation.getUser(),
+                        this.connectionInformation.getPassword());
+                this.session.setGlobalSocketTimeout(socketTimeout);
+
+                ServerList serverList = new ServerList(this.connectionInformation.getHost(),
+                        this.connectionInformation.getUser(),
+                        this.connectionInformation.getPassword(),
+                        this.connectionInformation.getDomain());
+                String clsIdFromProgId = serverList.getClsIdFromProgId(this.connectionInformation.getProgId());
+
+//                this.comServer = new JIComServer(JIProgId.valueOf(this.connectionInformation.getProgId()),this.connectionInformation.getHost(), this.session);
+
+                this.comServer = new JIComServer(
+                        JIClsid.valueOf(clsIdFromProgId),
+                        this.connectionInformation.getHost(), this.session);
+            } else {
+                throw new IllegalArgumentException("Neither clsid nor progid is valid!");
+            }
+
+            this.server = new OPCServer(this.comServer.createInstance());
+            this.errorMessageResolver = new ErrorMessageResolver(
+                    this.server.getCommon(), this.defaultLocaleID);
+        } catch (final UnknownHostException e) {
+            log.error("Unknown host when connecting to server", e);
+            cleanup();
+            throw e;
+        } catch (final JIException e) {
+            log.error("Failed to connect to server", e);
+            cleanup();
+            throw e;
+        } catch (final Throwable e) {
+            e.printStackTrace();
+            log.error("Unknown error", e);
+            cleanup();
+            throw new RuntimeException(e);
+        }
+
+        notifyConnectionStateChange(true);
+    }
+
+    /**
+     * cleanup after the connection is closed
+     */
+    protected void cleanup() {
+        log.debug("Destroying DCOM session...");
+        final JISession destructSession = this.session;
+        final Thread destructor = new Thread(new Runnable() {
+
+            public void run() {
+                final long ts = System.currentTimeMillis();
+                try {
+                    log.debug("Starting destruction of DCOM session");
+                    JISession.destroySession(destructSession);
+                    log.debug("Destructed DCOM session");
+                } catch (final Throwable e) {
+                    log.error("Failed to destruct DCOM session", e);
+                }
+            }
+        }, "UtgardSessionDestructor");
+        destructor.setName("OPCSessionDestructor");
+        destructor.setDaemon(true);
+        destructor.start();
+        log.debug("Destroying DCOM session... forked");
+
+        this.errorMessageResolver = null;
+        this.session = null;
+        this.comServer = null;
+        this.server = null;
+
+        this.groups.clear();
+    }
+
+    /**
+     * Disconnect the connection if it is connected
+     */
+    public synchronized void disconnect() {
+        if (!isConnected()) {
+            return;
+        }
+
+        try {
+            notifyConnectionStateChange(false);
+        } catch (final Throwable t) {
+        }
+
+        cleanup();
+    }
+
+    /**
+     * Dispose the connection in the case of an error
+     */
+    public void dispose() {
+        disconnect();
+    }
+
+    protected synchronized Group getGroup(final OPCGroupStateMgt groupMgt) throws JIException, IllegalArgumentException, UnknownHostException {
+        final Integer serverHandle = groupMgt.getState().getServerHandle();
+        if (this.groups.containsKey(serverHandle)) {
+            return this.groups.get(serverHandle);
+        } else {
+            final Group group = new Group(this, serverHandle, groupMgt);
+            this.groups.put(serverHandle, group);
+            return group;
+        }
+    }
+
+    /**
+     * Add a new named group to the server
+     *
+     * @param name The name of the group to use. Must be unique or
+     *             <code>null</code> so that the server creates a unique name.
+     * @return The new group
+     * @throws NotConnectedException    If the server is not connected using {@link Server#connect()}
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     * @throws DuplicateGroupException  If a group with this name already exists
+     */
+    public synchronized Group addGroup(final String name) throws NotConnectedException, IllegalArgumentException, UnknownHostException, JIException, DuplicateGroupException {
+        if (!isConnected()) {
+            throw new NotConnectedException();
+        }
+
+        try {
+            final OPCGroupStateMgt groupMgt = this.server.addGroup(name,
+                    this.defaultActive, this.defaultUpdateRate, 0,
+                    this.defaultTimeBias, this.defaultPercentDeadband,
+                    this.defaultLocaleID);
+            return getGroup(groupMgt);
+        } catch (final JIException e) {
+            if (e.getErrorCode() == 0xC004000C) {
+                throw new DuplicateGroupException();
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Add a new group and let the server generate a group name
+     * <p>
+     * Actually this method only calls {@link Server#addGroup(String)} with
+     * <code>null</code> as parameter.
+     *
+     * @return the new group
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws NotConnectedException
+     * @throws JIException
+     * @throws DuplicateGroupException
+     */
+    public Group addGroup() throws IllegalArgumentException,
+            UnknownHostException, NotConnectedException, JIException,
+            DuplicateGroupException {
+        return addGroup(null);
+    }
+
+    /**
+     * Find a group by its name
+     *
+     * @param name The name to look for
+     * @return The group
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     * @throws UnknownGroupException    If the group was not found
+     * @throws NotConnectedException    If the server is not connected
+     */
+    public Group findGroup(final String name) throws IllegalArgumentException, UnknownHostException, JIException, UnknownGroupException, NotConnectedException {
+        if (!isConnected()) {
+            throw new NotConnectedException();
+        }
+
+        try {
+            final OPCGroupStateMgt groupMgt = this.server.getGroupByName(name);
+            return getGroup(groupMgt);
+        } catch (final JIException e) {
+            switch (e.getErrorCode()) {
+                case 0x80070057:
+                    throw new UnknownGroupException(name);
+                default:
+                    throw e;
+            }
+        }
+    }
+
+    public int getDefaultLocaleID() {
+        return this.defaultLocaleID;
+    }
+
+    public void setDefaultLocaleID(final int defaultLocaleID) {
+        this.defaultLocaleID = defaultLocaleID;
+    }
+
+    public Float getDefaultPercentDeadband() {
+        return this.defaultPercentDeadband;
+    }
+
+    public void setDefaultPercentDeadband(final Float defaultPercentDeadband) {
+        this.defaultPercentDeadband = defaultPercentDeadband;
+    }
+
+    public Integer getDefaultTimeBias() {
+        return this.defaultTimeBias;
+    }
+
+    public void setDefaultTimeBias(final Integer defaultTimeBias) {
+        this.defaultTimeBias = defaultTimeBias;
+    }
+
+    public int getDefaultUpdateRate() {
+        return this.defaultUpdateRate;
+    }
+
+    public void setDefaultUpdateRate(final int defaultUpdateRate) {
+        this.defaultUpdateRate = defaultUpdateRate;
+    }
+
+    public boolean isDefaultActive() {
+        return this.defaultActive;
+    }
+
+    public void setDefaultActive(final boolean defaultActive) {
+        this.defaultActive = defaultActive;
+    }
+
+    /**
+     * Get the flat browser
+     *
+     * @return The flat browser or <code>null</code> if the functionality is not
+     * supported
+     */
+    public FlatBrowser getFlatBrowser() {
+        final OPCBrowseServerAddressSpace browser = this.server.getBrowser();
+        if (browser == null) {
+            return null;
+        }
+
+        return new FlatBrowser(browser);
+    }
+
+    /**
+     * Get the tree browser
+     *
+     * @return The tree browser or <code>null</code> if the functionality is not
+     * supported
+     * @throws JIException
+     */
+    public TreeBrowser getTreeBrowser() throws JIException {
+        final OPCBrowseServerAddressSpace browser = this.server.getBrowser();
+        if (browser == null) {
+            return null;
+        }
+
+        if (browser.queryOrganization() != OPCNAMESPACETYPE.OPC_NS_HIERARCHIAL) {
+            return null;
+        }
+
+        return new TreeBrowser(browser);
+    }
+
+    public synchronized String getErrorMessage(final int errorCode) {
+        if (this.errorMessageResolver == null) {
+            return String.format("Unknown error (%08X)", errorCode);
+        }
+
+        // resolve message
+        final String message = this.errorMessageResolver.getMessage(errorCode);
+
+        // and return if successfull
+        if (message != null) {
+            return message;
+        }
+
+        // return default message
+        return String.format("Unknown error (%08X)", errorCode);
+    }
+
+    public synchronized void addStateListener(
+            final ServerConnectionStateListener listener) {
+        this.stateListeners.add(listener);
+        listener.connectionStateChanged(isConnected());
+    }
+
+    public synchronized void removeStateListener(
+            final ServerConnectionStateListener listener) {
+        this.stateListeners.remove(listener);
+    }
+
+    protected void notifyConnectionStateChange(final boolean connected) {
+        final List<ServerConnectionStateListener> list = new ArrayList<ServerConnectionStateListener>(
+                this.stateListeners);
+        for (final ServerConnectionStateListener listener : list) {
+            listener.connectionStateChanged(connected);
+        }
+    }
+
+    public OPCSERVERSTATUS getServerState(final int timeout) throws Throwable {
+        return new ServerStateOperation(this.server).getServerState(timeout);
+    }
+
+    public OPCSERVERSTATUS getServerState() {
+        try {
+            return getServerState(2500);
+        } catch (final Throwable e) {
+            log.error("Server connection failed", e);
+            dispose();
+            return null;
+        }
+    }
+
+    public void removeGroup(final Group group, final boolean force)
+            throws JIException {
+        if (this.groups.containsKey(group.getServerHandle())) {
+            this.server.removeGroup(group.getServerHandle(), force);
+            this.groups.remove(group.getServerHandle());
+        }
+    }
+}

+ 18 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/ServerConnectionStateListener.java

@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+public interface ServerConnectionStateListener {
+    public abstract void connectionStateChanged(boolean connected);
+}

+ 20 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/ServerStateListener.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import org.openscada.opc.dcom.da.OPCSERVERSTATUS;
+
+public interface ServerStateListener {
+    public void stateUpdate(OPCSERVERSTATUS state);
+}

+ 103 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/ServerStateOperation.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.openscada.opc.dcom.da.OPCSERVERSTATUS;
+import org.openscada.opc.dcom.da.impl.OPCServer;
+
+/**
+ * A server state operation which can be interruped
+ *
+ * @author Jens Reimann
+ */
+@Slf4j
+public class ServerStateOperation implements Runnable {
+
+    public OPCSERVERSTATUS _serverStatus = null;
+
+    public OPCServer _server;
+
+    public Throwable _error;
+
+    public Object _lock = new Object();
+
+    public boolean _running = false;
+
+    public ServerStateOperation(final OPCServer server) {
+        super();
+        this._server = server;
+    }
+
+    /**
+     * Perform the operation.
+     * <p>
+     * This method will block until either the serve state has been aquired or the
+     * timeout triggers cancels the call.
+     * </p>
+     */
+    public void run() {
+        synchronized (this._lock) {
+            this._running = true;
+        }
+        try {
+            this._serverStatus = this._server.getStatus();
+            synchronized (this._lock) {
+                this._running = false;
+                this._lock.notify();
+            }
+        } catch (Throwable e) {
+            log.info("Failed to get server state", e);
+            this._error = e;
+            this._running = false;
+            synchronized (this._lock) {
+                this._lock.notify();
+            }
+        }
+
+    }
+
+    /**
+     * Get the server state with a timeout.
+     *
+     * @param timeout the timeout in ms
+     * @return the server state or <code>null</code> if the server is not set.
+     * @throws Throwable any error that occurred
+     */
+    public OPCSERVERSTATUS getServerState(final int timeout) throws Throwable {
+        if (this._server == null) {
+            log.debug("No connection to server. Skipping...");
+            return null;
+        }
+
+        Thread t = new Thread(this, "OPCServerStateReader");
+
+        synchronized (this._lock) {
+            t.start();
+            this._lock.wait(timeout);
+            if (this._running) {
+                log.warn("State operation still running. Interrupting...");
+                t.interrupt();
+                throw new InterruptedException("Interrupted getting server state");
+            }
+        }
+        if (this._error != null) {
+            log.warn("An error occurred while getting server state", this._error);
+            throw this._error;
+        }
+
+        return this._serverStatus;
+    }
+
+}

+ 91 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/ServerStateReader.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.openscada.opc.dcom.da.OPCSERVERSTATUS;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class ServerStateReader {
+
+    private Server _server = null;
+
+    private ScheduledExecutorService _scheduler = null;
+
+    private final List<ServerStateListener> _listeners = new CopyOnWriteArrayList<ServerStateListener>();
+
+    private ScheduledFuture<?> _job = null;
+
+    public ServerStateReader(final Server server) {
+        super();
+        this._server = server;
+        this._scheduler = this._server.getScheduler();
+    }
+
+    /**
+     * Create a new server state reader. Please note that the scheduler might get
+     * blocked for a short period of time in case of a connection failure!
+     *
+     * @param server    the server to check
+     * @param scheduler the scheduler to use
+     */
+    public ServerStateReader(final Server server, final ScheduledExecutorService scheduler) {
+        super();
+        this._server = server;
+        this._scheduler = scheduler;
+    }
+
+    public synchronized void start() {
+        if (this._job != null) {
+            return;
+        }
+
+        this._job = this._scheduler.scheduleAtFixedRate(new Runnable() {
+
+            public void run() {
+                once();
+            }
+        }, 1000, 1000, TimeUnit.MILLISECONDS);
+    }
+
+    public synchronized void stop() {
+        this._job.cancel(false);
+        this._job = null;
+    }
+
+    protected void once() {
+        log.debug("Reading server state");
+
+        final OPCSERVERSTATUS state = this._server.getServerState();
+
+        for (final ServerStateListener listener : new ArrayList<ServerStateListener>(this._listeners)) {
+            listener.stateUpdate(state);
+        }
+    }
+
+    public void addListener(final ServerStateListener listener) {
+        this._listeners.add(listener);
+    }
+
+    public void removeListener(final ServerStateListener listener) {
+        this._listeners.remove(listener);
+    }
+}

+ 95 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/SyncAccess.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.openscada.opc.lib.common.NotConnectedException;
+
+import java.net.UnknownHostException;
+import java.util.Map;
+
+@Slf4j
+public class SyncAccess extends AccessBase implements Runnable {
+
+    private Thread runner = null;
+
+    private Throwable lastError = null;
+
+    public SyncAccess(final Server server, final int period) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException {
+        super(server, period);
+    }
+
+    public SyncAccess(final Server server, final int period, final String logTag) throws IllegalArgumentException, UnknownHostException, NotConnectedException, JIException, DuplicateGroupException {
+        super(server, period, logTag);
+    }
+
+    public void run() {
+        while (this.active) {
+            try {
+                runOnce();
+                if (this.lastError != null) {
+                    this.lastError = null;
+                    handleError(null);
+                }
+            } catch (Throwable e) {
+                log.error("Sync read failed", e);
+                handleError(e);
+                this.server.disconnect();
+            }
+
+            try {
+                Thread.sleep(getPeriod());
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    protected void runOnce() throws JIException {
+        if (!this.active || this.group == null) {
+            return;
+        }
+
+        Map<Item, ItemState> result;
+
+        // lock only this section since we could get into a deadlock otherwise
+        // calling updateItem
+        synchronized (this) {
+            Item[] items = this.items.keySet().toArray(new Item[this.items.size()]);
+            result = this.group.read(false, items);
+        }
+
+        for (Map.Entry<Item, ItemState> entry : result.entrySet()) {
+            updateItem(entry.getKey(), entry.getValue());
+        }
+
+    }
+
+    @Override
+    protected synchronized void start() throws JIException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException {
+        super.start();
+
+        this.runner = new Thread(this, "UtgardSyncReader");
+        this.runner.setDaemon(true);
+        this.runner.start();
+    }
+
+    @Override
+    protected synchronized void stop() throws JIException {
+        super.stop();
+
+        this.runner = null;
+        this.items.clear();
+    }
+}

+ 37 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/UnknownGroupException.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+public class UnknownGroupException extends Exception {
+    private String _name = null;
+
+    public UnknownGroupException(final String name) {
+        super();
+        this._name = name;
+    }
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1771564928794033075L;
+
+    public String getName() {
+        return this._name;
+    }
+
+    public void setName(final String name) {
+        this._name = name;
+    }
+
+}

+ 36 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/WriteRequest.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da;
+
+import org.jinterop.dcom.core.JIVariant;
+
+public class WriteRequest {
+    private Item _item = null;
+
+    private JIVariant _value = null;
+
+    public WriteRequest(final Item item, final JIVariant value) {
+        super();
+        this._item = item;
+        this._value = value;
+    }
+
+    public Item getItem() {
+        return this._item;
+    }
+
+    public JIVariant getValue() {
+        return this._value;
+    }
+}

+ 29 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/browser/Access.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da.browser;
+
+public enum Access {
+    READ(1),
+    WRITE(2);
+
+    private int _code = 0;
+
+    private Access(final int code) {
+        this._code = code;
+    }
+
+    public int getCode() {
+        return this._code;
+    }
+}

+ 112 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/browser/BaseBrowser.java

@@ -0,0 +1,112 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da.browser;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jinterop.dcom.common.JIException;
+import org.openscada.opc.dcom.common.impl.EnumString;
+import org.openscada.opc.dcom.da.OPCBROWSETYPE;
+import org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace;
+
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.EnumSet;
+
+/**
+ * A class implementing base browsing
+ *
+ * @author Jens Reimann
+ */
+@Slf4j
+public class BaseBrowser {
+
+    protected OPCBrowseServerAddressSpace _browser;
+
+    /**
+     * The batch size is the number of entries that will be requested with one call
+     * from the server. Sometimes too big batch sizes will cause an exception. And
+     * smaller batch sizes degrade perfomance. The default is set by {@link EnumString#DEFAULT_BATCH_SIZE}
+     * and can be overridden by the java property <q>openscada.dcom.enum-batch-size</q>.
+     */
+    protected int _batchSize;
+
+    public BaseBrowser(final OPCBrowseServerAddressSpace browser) {
+        this(browser, EnumString.DEFAULT_BATCH_SIZE);
+    }
+
+    public BaseBrowser(final OPCBrowseServerAddressSpace browser, final int batchSize) {
+        super();
+        this._browser = browser;
+        this._batchSize = batchSize;
+    }
+
+    /**
+     * Set the batch size
+     *
+     * @param batchSize The new batch size
+     */
+    public void setBatchSize(final int batchSize) {
+        this._batchSize = batchSize;
+    }
+
+    /**
+     * Get the batch size
+     *
+     * @return the current batch size
+     */
+    public int getBatchSize() {
+        return this._batchSize;
+    }
+
+    /**
+     * Perform the browse operation.
+     *
+     * @param type
+     * @param filterCriteria
+     * @param accessMask
+     * @param variantType
+     * @return The browse result
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    protected Collection<String> browse(final OPCBROWSETYPE type, final String filterCriteria, final EnumSet<Access> accessMask, final int variantType) throws IllegalArgumentException, UnknownHostException, JIException {
+        int accessMaskValue = 0;
+
+        if (accessMask.contains(Access.READ)) {
+            accessMaskValue |= Access.READ.getCode();
+        }
+        if (accessMask.contains(Access.WRITE)) {
+            accessMaskValue |= Access.WRITE.getCode();
+        }
+
+        log.debug("Browsing with a batch size of " + this._batchSize);
+
+        return this._browser.browse(type, filterCriteria, accessMaskValue, variantType).asCollection(this._batchSize);
+    }
+
+    /**
+     * Browse the access paths for one item.
+     *
+     * @param itemId The item ID to look up the access paths
+     * @return The collection of the access paths
+     * @throws JIException
+     * @throws UnknownHostException
+     * @throws IllegalArgumentException
+     */
+    public Collection<String> getAccessPaths(final String itemId) throws IllegalArgumentException, UnknownHostException, JIException {
+        return this._browser.browseAccessPaths(itemId).asCollection(this._batchSize);
+    }
+
+}

+ 108 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/browser/Branch.java

@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da.browser;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+
+public class Branch {
+    private Branch _parent = null;
+
+    private String _name = null;
+
+    private Collection<Branch> _branches = new LinkedList<Branch>();
+
+    private Collection<Leaf> _leaves = new LinkedList<Leaf>();
+
+    /**
+     * Create a branch to the virtual root folder
+     */
+    public Branch() {
+        super();
+    }
+
+    /**
+     * Create a branch with a parent branch and a name of this branch.
+     *
+     * @param parent The parent of this branch
+     * @param name   The name of this branch
+     */
+    public Branch(final Branch parent, final String name) {
+        super();
+        this._name = name;
+        this._parent = parent;
+    }
+
+    /**
+     * Get all branches.
+     * <br/>
+     * They must be filled first with a fill method from the {@link TreeBrowser}
+     *
+     * @return The list of branches
+     */
+    public Collection<Branch> getBranches() {
+        return this._branches;
+    }
+
+    public void setBranches(final Collection<Branch> branches) {
+        this._branches = branches;
+    }
+
+    /**
+     * Get all leaves.
+     * <br/>
+     * They must be filled first with a fill method from the {@link TreeBrowser}
+     *
+     * @return The list of leaves
+     */
+    public Collection<Leaf> getLeaves() {
+        return this._leaves;
+    }
+
+    public void setLeaves(final Collection<Leaf> leaves) {
+        this._leaves = leaves;
+    }
+
+    public String getName() {
+        return this._name;
+    }
+
+    public void setName(final String name) {
+        this._name = name;
+    }
+
+    public Branch getParent() {
+        return this._parent;
+    }
+
+    /**
+     * Get the list of names from the parent up to this branch
+     *
+     * @return The stack of branch names from the parent up this one
+     */
+    public Collection<String> getBranchStack() {
+        LinkedList<String> branches = new LinkedList<String>();
+
+        Branch currentBranch = this;
+        while (currentBranch.getParent() != null) {
+            branches.add(currentBranch.getName());
+            currentBranch = currentBranch.getParent();
+        }
+
+        Collections.reverse(branches);
+        return branches;
+    }
+
+}

+ 66 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/browser/FlatBrowser.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da.browser;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIVariant;
+import org.openscada.opc.dcom.da.OPCBROWSETYPE;
+import org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace;
+
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.EnumSet;
+
+/**
+ * Browse through the flat server namespace
+ *
+ * @author Jens Reimann <jens.reimann@th4-systems.com>
+ */
+public class FlatBrowser extends BaseBrowser {
+    public FlatBrowser(final OPCBrowseServerAddressSpace browser) {
+        super(browser);
+    }
+
+    public FlatBrowser(final OPCBrowseServerAddressSpace browser, final int batchSize) {
+        super(browser, batchSize);
+    }
+
+    /**
+     * Perform a flat browse operation
+     *
+     * @param filterCriteria The filter criteria. Use an empty string if you don't need one.
+     * @param accessMask     The access mask. An empty set will search for all.
+     * @param variantType    The variant type. Must be one of the <code>VT_</code> constants of {@link JIVariant}. Use {@link JIVariant#VT_EMPTY} if you want to browse for all.
+     * @return The list of entries
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public Collection<String> browse(final String filterCriteria, final EnumSet<Access> accessMask, final int variantType) throws IllegalArgumentException, UnknownHostException, JIException {
+        return browse(OPCBROWSETYPE.OPC_FLAT, filterCriteria, accessMask, variantType);
+    }
+
+    public Collection<String> browse(final String filterCriteria) throws IllegalArgumentException, UnknownHostException, JIException {
+        return browse(filterCriteria, EnumSet.noneOf(Access.class), JIVariant.VT_EMPTY);
+    }
+
+    public Collection<String> browse() throws IllegalArgumentException, UnknownHostException, JIException {
+        return browse("", EnumSet.noneOf(Access.class), JIVariant.VT_EMPTY);
+    }
+
+    public Collection<String> browse(final EnumSet<Access> accessMask) throws IllegalArgumentException, UnknownHostException, JIException {
+        return browse("", accessMask, JIVariant.VT_EMPTY);
+    }
+
+}

+ 54 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/browser/Leaf.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da.browser;
+
+public class Leaf {
+    private Branch _parent = null;
+
+    private String _name = "";
+
+    private String _itemId = null;
+
+    public Leaf(final Branch parent, final String name) {
+        this._parent = parent;
+        this._name = name;
+    }
+
+    public Leaf(final Branch parent, final String name, final String itemId) {
+        this._parent = parent;
+        this._name = name;
+        this._itemId = itemId;
+    }
+
+    public String getItemId() {
+        return this._itemId;
+    }
+
+    public void setItemId(final String itemId) {
+        this._itemId = itemId;
+    }
+
+    public String getName() {
+        return this._name;
+    }
+
+    public void setName(final String name) {
+        this._name = name;
+    }
+
+    public Branch getParent() {
+        return this._parent;
+    }
+
+}

+ 225 - 0
platform-opc/src/main/java/org/openscada/opc/lib/da/browser/TreeBrowser.java

@@ -0,0 +1,225 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.da.browser;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIVariant;
+import org.openscada.opc.dcom.da.OPCBROWSEDIRECTION;
+import org.openscada.opc.dcom.da.OPCBROWSETYPE;
+import org.openscada.opc.dcom.da.impl.OPCBrowseServerAddressSpace;
+
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.LinkedList;
+
+/**
+ * Browse through the hierarchical server namespace.
+ * <br/>
+ * The operations on the address space browser browser are not synchronized
+ * as is the TreeBrowser itself. The user must take care of preventing
+ * simultanious access to this instance and the server address space browser.
+ *
+ * @author Jens Reimann <jens.reimann@th4-systems.com>
+ */
+public class TreeBrowser extends BaseBrowser {
+
+    private String _filterCriteria = "";
+
+    private EnumSet<Access> _accessMask = EnumSet.noneOf(Access.class);
+
+    private int _variantType = JIVariant.VT_EMPTY;
+
+    /**
+     * Browse for all items without search parameters.
+     * <br/>
+     * This will actually call:
+     * <br/>
+     * <code>
+     * TreeBrowser ( browser, "", EnumSet.noneOf ( Access.class ), JIVariant.VT_EMPTY );
+     * </code>
+     *
+     * @param browser The browser to use for browsing
+     */
+    public TreeBrowser(final OPCBrowseServerAddressSpace browser) {
+        super(browser);
+    }
+
+    /**
+     * Browse for items with search parameters.
+     *
+     * @param browser        The browser to use
+     * @param filterCriteria The filter criteria. It is specific to the server you use.
+     * @param accessMask     The access mask (use <code>EnumSet.noneOf ( Access.class )</code> for all)
+     * @param variantType    The variant type (use <code>JIVariant.VT_EMPTY</code> for all)
+     */
+    public TreeBrowser(final OPCBrowseServerAddressSpace browser, final String filterCriteria, final EnumSet<Access> accessMask, final int variantType) {
+        super(browser);
+        this._filterCriteria = filterCriteria;
+        this._accessMask = accessMask;
+        this._variantType = variantType;
+    }
+
+    /**
+     * Move the tree browser to the root folder
+     *
+     * @throws JIException
+     */
+    protected void moveToRoot() throws JIException {
+        this._browser.changePosition(null, OPCBROWSEDIRECTION.OPC_BROWSE_TO);
+    }
+
+    /**
+     * Move the tree browser to a branch
+     *
+     * @param branch The branch to move to
+     * @throws JIException
+     */
+    protected void moveToBranch(final Branch branch) throws JIException {
+        Collection<String> branchStack = branch.getBranchStack();
+
+        moveToRoot();
+        for (String branchName : branchStack) {
+            this._browser.changePosition(branchName, OPCBROWSEDIRECTION.OPC_BROWSE_DOWN);
+        }
+    }
+
+    /**
+     * Browse the root branch for its sub-branches.
+     *
+     * @return The list of sub branches
+     * @throws JIException
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     */
+    public Branch browseBranches() throws JIException, IllegalArgumentException, UnknownHostException {
+        Branch branch = new Branch();
+        fillBranches(branch);
+        return branch;
+    }
+
+    /**
+     * Browse the root branch for this leaves.
+     *
+     * @return The list of leaves
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public Branch browseLeaves() throws IllegalArgumentException, UnknownHostException, JIException {
+        Branch branch = new Branch();
+        fillLeaves(branch);
+        return branch;
+    }
+
+    /**
+     * Fill the branch list of the provided branch.
+     *
+     * @param branch The branch to fill.
+     * @throws JIException
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     */
+    public void fillBranches(final Branch branch) throws JIException, IllegalArgumentException, UnknownHostException {
+        moveToBranch(branch);
+        browse(branch, false, true, false);
+    }
+
+    /**
+     * Fill the leaf list of the provided branch.
+     *
+     * @param branch The branch to fill.
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public void fillLeaves(final Branch branch) throws IllegalArgumentException, UnknownHostException, JIException {
+        moveToBranch(branch);
+        browse(branch, true, false, false);
+    }
+
+    /**
+     * Browse through all levels of the tree browser.
+     *
+     * @return The whole expanded server address space
+     * @throws JIException
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     */
+    public Branch browse() throws JIException, IllegalArgumentException, UnknownHostException {
+        Branch branch = new Branch();
+        fill(branch);
+        return branch;
+    }
+
+    /**
+     * Fill the leaves and branches of the branch provided branches including
+     * alls sub-branches.
+     *
+     * @param branch The branch to fill.
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public void fill(final Branch branch) throws IllegalArgumentException, UnknownHostException, JIException {
+        moveToBranch(branch);
+        browse(branch, true, true, true);
+    }
+
+    /**
+     * Fill the branch object with the leaves of this currently selected branch.
+     * <br/>
+     * The server object is not located to the branch before browsing!
+     *
+     * @param branch The branch to fill
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    protected void browseLeaves(final Branch branch) throws IllegalArgumentException, UnknownHostException, JIException {
+        branch.setLeaves(new LinkedList<Leaf>());
+
+        for (String item : browse(OPCBROWSETYPE.OPC_LEAF, this._filterCriteria, this._accessMask, this._variantType)) {
+            Leaf leaf = new Leaf(branch, item, this._browser.getItemID(item));
+            branch.getLeaves().add(leaf);
+        }
+    }
+
+    protected void browseBranches(final Branch branch, final boolean leaves, final boolean descend) throws IllegalArgumentException, UnknownHostException, JIException {
+        branch.setBranches(new LinkedList<Branch>());
+
+        for (String item : browse(OPCBROWSETYPE.OPC_BRANCH, this._filterCriteria, this._accessMask, this._variantType)) {
+            Branch subBranch = new Branch(branch, item);
+            // descend only if we should
+            if (descend) {
+                this._browser.changePosition(item, OPCBROWSEDIRECTION.OPC_BROWSE_DOWN);
+                browse(subBranch, leaves, true, true);
+                this._browser.changePosition(null, OPCBROWSEDIRECTION.OPC_BROWSE_UP);
+            }
+            branch.getBranches().add(subBranch);
+        }
+    }
+
+    protected void browse(final Branch branch, final boolean leaves, final boolean branches, final boolean descend) throws IllegalArgumentException, UnknownHostException, JIException {
+        // process leaves
+        if (leaves) {
+            browseLeaves(branch);
+        }
+
+        // process branches
+        if (branches) {
+            browseBranches(branch, leaves, descend);
+        }
+    }
+}

+ 36 - 0
platform-opc/src/main/java/org/openscada/opc/lib/list/Categories.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.list;
+
+public interface Categories {
+    /**
+     * Category of the OPC DA 1.0 Servers
+     */
+    public final static Category OPCDAServer10 = new Category(org.openscada.opc.dcom.common.Categories.OPCDAServer10);
+
+    /**
+     * Category of the OPC DA 2.0 Servers
+     */
+    public final static Category OPCDAServer20 = new Category(org.openscada.opc.dcom.common.Categories.OPCDAServer20);
+
+    /**
+     * Category of the OPC DA 3.0 Servers
+     */
+    public final static Category OPCDAServer30 = new Category(org.openscada.opc.dcom.common.Categories.OPCDAServer30);
+
+    /**
+     * Category of the XML DA 1.0 Servers
+     */
+    public final static Category XMLDAServer10 = new Category(org.openscada.opc.dcom.common.Categories.XMLDAServer10);
+}

+ 58 - 0
platform-opc/src/main/java/org/openscada/opc/lib/list/Category.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.list;
+
+public class Category {
+    private String _catId = null;
+
+    public Category(final String catId) {
+        this._catId = catId;
+    }
+
+    @Override
+    public String toString() {
+        return this._catId;
+    }
+
+    @Override
+    public int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + (this._catId == null ? 0 : this._catId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final Category other = (Category) obj;
+        if (this._catId == null) {
+            if (other._catId != null) {
+                return false;
+            }
+        } else if (!this._catId.equals(other._catId)) {
+            return false;
+        }
+        return true;
+    }
+
+}

+ 175 - 0
platform-opc/src/main/java/org/openscada/opc/lib/list/ServerList.java

@@ -0,0 +1,175 @@
+/*
+ * Copyright 2016-2021 Pnoker. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openscada.opc.lib.list;
+
+import org.jinterop.dcom.common.JIException;
+import org.jinterop.dcom.core.JIClsid;
+import org.jinterop.dcom.core.JIComServer;
+import org.jinterop.dcom.core.JISession;
+import org.openscada.opc.dcom.list.ClassDetails;
+import org.openscada.opc.dcom.list.Constants;
+import org.openscada.opc.dcom.list.impl.OPCServerList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import rpc.core.UUID;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A wrapper around the {@link OPCServerList} class which makes the handling somewhat easier.
+ *
+ * @author Jens Reimann &lt;jens.reimann@th4-systems.com&gt;
+ * @since 0.1.8
+ */
+public class ServerList {
+    private final Logger logger = LoggerFactory.getLogger(ServerList.class);
+    private final JISession _session;
+
+    private final OPCServerList _serverList;
+
+    /**
+     * Create a new instance with an already existing session
+     *
+     * @param session the DCOM session
+     * @param host    the host to query
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public ServerList(final JISession session, final String host) throws IllegalArgumentException, UnknownHostException, JIException {
+        this._session = session;
+        JIComServer comServer = new JIComServer(JIClsid.valueOf(Constants.OPCServerList_CLSID), host, this._session);
+        this._serverList = new OPCServerList(comServer.createInstance());
+    }
+
+    /**
+     * Create a new instance and a new DCOM session
+     *
+     * @param host     the host to contact
+     * @param user     the user to use for authentication
+     * @param password the password to use for authentication
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public ServerList(final String host, final String user, final String password) throws IllegalArgumentException, UnknownHostException, JIException {
+        this(host, user, password, null);
+    }
+
+    /**
+     * Create a new instance and a new DCOM session
+     *
+     * @param host     the host to contact
+     * @param user     the user to use for authentication
+     * @param password the password to use for authentication
+     * @param domain   The domain to use for authentication
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public ServerList(final String host, final String user, final String password, final String domain) throws IllegalArgumentException, UnknownHostException, JIException {
+        this(JISession.createSession(domain, user, password), host);
+    }
+
+    /**
+     * Get the details of a opc class
+     *
+     * @param clsId the class to request details for
+     * @return The class details
+     * @throws JIException
+     */
+    public ClassDetails getDetails(final String clsId) throws JIException {
+        return this._serverList.getClassDetails(JIClsid.valueOf(clsId));
+    }
+
+    /**
+     * Fetch the class id of a prog id
+     *
+     * @param progId The prog id to look up
+     * @return the class id or <code>null</code> if none could be found.
+     * @throws JIException
+     */
+    public String getClsIdFromProgId(final String progId) throws JIException {
+        JIClsid cls = this._serverList.getCLSIDFromProgID(progId);
+        if (cls == null) {
+            return null;
+        }
+        return cls.getCLSID();
+    }
+
+    /**
+     * List all servers that match the requirements
+     *
+     * @param implemented All implemented categories
+     * @param required    All required categories
+     * @return A collection of <q>class ids</q>
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public Collection<String> listServers(final Category[] implemented, final Category[] required) throws IllegalArgumentException, UnknownHostException, JIException {
+        // convert the type safe categories to plain UUIDs
+        UUID[] u1 = new UUID[implemented.length];
+        UUID[] u2 = new UUID[required.length];
+
+        for (int i = 0; i < implemented.length; i++) {
+            u1[i] = new UUID(implemented[i].toString());
+        }
+
+        for (int i = 0; i < required.length; i++) {
+            u2[i] = new UUID(required[i].toString());
+        }
+
+        // get them as UUIDs
+        Collection<UUID> resultU = this._serverList.enumClassesOfCategories(u1, u2).asCollection();
+
+        // and convert to easier usable strings
+        Collection<String> result = new ArrayList<String>(resultU.size());
+        for (UUID uuid : resultU) {
+            result.add(uuid.toString());
+        }
+        return result;
+    }
+
+    /**
+     * List all servers that match the requirements and return the class details
+     *
+     * @param implemented All implemented categories
+     * @param required    All required categories
+     * @return a collection of matching server and their class information
+     * @throws IllegalArgumentException
+     * @throws UnknownHostException
+     * @throws JIException
+     */
+    public Collection<ClassDetails> listServersWithDetails(final Category[] implemented, final Category[] required) throws IllegalArgumentException, UnknownHostException, JIException {
+        Collection<String> resultString = listServers(implemented, required);
+
+        List<ClassDetails> result = new ArrayList<ClassDetails>(resultString.size());
+
+        for (String clsId : resultString) {
+            //TODO 注册表没清理干净会报错 ,只做了异常处理
+            try {
+                result.add(getDetails(clsId));
+            }catch (JIException e){
+                logger.error(clsId+":Message not found for errorCode: 0x80040153");
+                e.printStackTrace();
+            }
+        }
+
+        return result;
+    }
+}

+ 6 - 0
platform-opc/src/main/resources/opc.properties

@@ -0,0 +1,6 @@
+host=192.168.108.108
+username=Administrator
+password=Hollysys
+domain=
+clsid=001AAAA6-FB54-4627-84B2-8777379E5868
+progid=Hollysys.HOLLiASiComm.1