Refactor IEC 60870 server data model

This change refactors the IEC 60870 server side data model in order to
provide more common functionality by default for implementing custom
data models.
diff --git a/org.eclipse.neoscada.core.common.iec60870/META-INF/MANIFEST.MF b/org.eclipse.neoscada.core.common.iec60870/META-INF/MANIFEST.MF
index 30a14fd..5e4a39c 100644
--- a/org.eclipse.neoscada.core.common.iec60870/META-INF/MANIFEST.MF
+++ b/org.eclipse.neoscada.core.common.iec60870/META-INF/MANIFEST.MF
@@ -5,7 +5,7 @@
 Bundle-Version: 0.4.0.qualifier
 Bundle-Vendor: Eclipse NeoSCADA
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.neoscada.protocol.iec60870;version="1.3.0",
+Import-Package: org.eclipse.neoscada.protocol.iec60870;version="0.4.0",
  org.eclipse.scada.ca;version="0.2.0",
  org.eclipse.scada.utils.beans;version="0.2.0"
-Export-Package: org.eclipse.neoscada.core.common.iec60870;version="1.3.0";uses:="org.eclipse.neoscada.protocol.iec60870,org.eclipse.scada.ca"
+Export-Package: org.eclipse.neoscada.core.common.iec60870;version="0.4.0";uses:="org.eclipse.neoscada.protocol.iec60870,org.eclipse.scada.ca"
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/.classpath b/org.eclipse.neoscada.da.server.exporter.iec60870/.classpath
index 098194c..eca7bdb 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/.classpath
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/.classpath
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="output" path="bin"/>
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.neoscada.da.server.exporter.iec60870/.settings/org.eclipse.jdt.core.prefs
index f42de36..0c68a61 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/.settings/org.eclipse.jdt.core.prefs
@@ -1,7 +1,7 @@
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/META-INF/MANIFEST.MF b/org.eclipse.neoscada.da.server.exporter.iec60870/META-INF/MANIFEST.MF
index 4c81313..df8deb9 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/META-INF/MANIFEST.MF
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/META-INF/MANIFEST.MF
@@ -4,17 +4,18 @@
 Bundle-SymbolicName: org.eclipse.neoscada.da.server.exporter.iec60870
 Bundle-Version: 0.4.0.qualifier
 Bundle-Vendor: Eclipse NeoSCADA
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.google.common.base;version="12.0.0",
  com.google.common.util.concurrent;version="12.0.0",
- org.eclipse.neoscada.core.common.iec60870;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.asdu;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.asdu.types;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.io;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.server;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.server.data;version="1.4.0",
- org.eclipse.neoscada.protocol.iec60870.server.data.event;version="1.4.0",
+ org.eclipse.neoscada.core.common.iec60870;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.asdu;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.asdu.types;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.io;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.server;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.server.data;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.server.data.event;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.server.data.model;version="0.4.0",
  org.eclipse.scada.ca;version="0.2.0",
  org.eclipse.scada.ca.common.factory;version="0.1.0",
  org.eclipse.scada.core;version="0.1.0",
@@ -36,3 +37,4 @@
  org.osgi.framework;version="1.7.0",
  org.slf4j;version="1.7.2"
 Service-Component: OSGI-INF/factory.xml
+Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/BufferingChangeModel.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/BufferingChangeModel.java
deleted file mode 100644
index 43c32fd..0000000
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/BufferingChangeModel.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 IBH SYSTEMS GmbH and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *     IBH SYSTEMS GmbH - initial API and implementation
- *******************************************************************************/
-package org.eclipse.neoscada.da.server.exporter.iec60870;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.ASDUAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationEntry;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationObjectAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.Value;
-
-/**
- * This is a change model which sends out aggregated change events
- */
-public class BufferingChangeModel implements ChangeModel
-{
-    public static interface Context
-    {
-        public void notifyBoolean ( ASDUAddress key, List<InformationEntry<Boolean>> booleans );
-
-        public void notifyFloat ( ASDUAddress key, List<InformationEntry<Float>> floats );
-    }
-
-    private final Context context;
-
-    private final ScheduledExecutorService executor;
-
-    private final long flushDelay;
-
-    private Map<ASDUAddress, Map<InformationObjectAddress, Value<?>>> cache = new HashMap<> ();
-
-    private ScheduledFuture<?> future;
-
-    /**
-     * Create a new buffering change model
-     *
-     * @param executor
-     *            a single threaded scheduled executor
-     * @param flushDelay
-     *            the minimum delay between flushes
-     */
-    public BufferingChangeModel ( final Context context, final ScheduledExecutorService executor, final long flushDelay )
-    {
-        this.context = context;
-        this.executor = executor;
-        this.flushDelay = flushDelay;
-    }
-
-    @Override
-    public synchronized void notifyChange ( final ASDUAddress asduAddress, final InformationObjectAddress informationObjectAddress, final Value<?> value )
-    {
-        Map<InformationObjectAddress, Value<?>> asduCache = this.cache.get ( asduAddress );
-        if ( asduCache == null )
-        {
-            asduCache = new HashMap<> ();
-            this.cache.put ( asduAddress, asduCache );
-        }
-
-        if ( asduCache.containsKey ( value ) )
-        {
-            // we would overwrite data, flush immediately
-
-            final Map<ASDUAddress, Map<InformationObjectAddress, Value<?>>> cache = this.cache;
-            this.cache = new HashMap<> ();
-
-            // create a new entry since we just created a new cache map
-
-            asduCache = new HashMap<> ();
-            this.cache.put ( asduAddress, asduCache );
-
-            // flush, not holding the lock, and in order with other flushes
-
-            this.executor.execute ( new Runnable () {
-                @Override
-                public void run ()
-                {
-                    performFlush ( cache );
-                }
-            } );
-        }
-
-        // add to cache
-
-        asduCache.put ( informationObjectAddress, value );
-
-        // trigger flush
-
-        triggerFlush ();
-    }
-
-    /**
-     * Trigger a flush
-     * <p>
-     * If a flush is already pending, then nothing will happen
-     * </p>
-     */
-    private synchronized void triggerFlush ()
-    {
-        if ( this.future != null )
-        {
-            // there is already a flush pending
-            return;
-        }
-
-        // schedule new flush
-
-        this.future = this.executor.schedule ( new Runnable () {
-            @Override
-            public void run ()
-            {
-                flush ();
-            }
-        }, this.flushDelay, TimeUnit.MILLISECONDS );
-    }
-
-    /**
-     * Flush the current buffer
-     */
-    private void flush ()
-    {
-        Map<ASDUAddress, Map<InformationObjectAddress, Value<?>>> cache;
-        synchronized ( this )
-        {
-            if ( this.future == null )
-            {
-                return;
-            }
-
-            // replace cache with new one
-
-            cache = this.cache;
-            this.cache = new HashMap<> ();
-
-            this.future = null;
-        }
-
-        // flush outside the lock
-
-        performFlush ( cache );
-    }
-
-    @SuppressWarnings ( "unchecked" )
-    private void performFlush ( final Map<ASDUAddress, Map<InformationObjectAddress, Value<?>>> cache )
-    {
-        for ( final Map.Entry<ASDUAddress, Map<InformationObjectAddress, Value<?>>> entry : cache.entrySet () )
-        {
-            List<InformationEntry<Boolean>> booleans = null;
-            List<InformationEntry<Float>> floats = null;
-
-            for ( final Map.Entry<InformationObjectAddress, Value<?>> valueEntry : entry.getValue ().entrySet () )
-            {
-                final InformationObjectAddress ioa = valueEntry.getKey ();
-                final Value<?> value = valueEntry.getValue ();
-
-                if ( value.getValue () instanceof Boolean )
-                {
-                    booleans = add ( booleans, ioa, (Value<Boolean>)value );
-                }
-                else if ( value.getValue () instanceof Float )
-                {
-                    floats = add ( floats, ioa, (Value<Float>)value );
-                }
-            }
-
-            // send out what we have sorted out
-
-            if ( booleans != null )
-            {
-                this.context.notifyBoolean ( entry.getKey (), booleans );
-            }
-            if ( floats != null )
-            {
-                this.context.notifyFloat ( entry.getKey (), floats );
-            }
-        }
-    }
-
-    private <T> List<InformationEntry<T>> add ( List<InformationEntry<T>> values, final InformationObjectAddress ioa, final Value<T> value )
-    {
-        if ( values == null )
-        {
-            values = new LinkedList<> ();
-        }
-
-        values.add ( new InformationEntry<> ( ioa, value ) );
-
-        return values;
-    }
-
-    @Override
-    public void dispose ()
-    {
-        synchronized ( this )
-        {
-            if ( this.future != null )
-            {
-                this.future.cancel ( false );
-                this.future = null;
-            }
-        }
-
-        // pending data will get discarded, this is ok
-    }
-
-}
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ChangeModel.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ChangeModel.java
deleted file mode 100644
index adfa422..0000000
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ChangeModel.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 IBH SYSTEMS GmbH and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *     IBH SYSTEMS GmbH - initial API and implementation
- *******************************************************************************/
-package org.eclipse.neoscada.da.server.exporter.iec60870;
-
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.ASDUAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationObjectAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.Value;
-
-public interface ChangeModel
-{
-    public void notifyChange ( ASDUAddress asduAddress, InformationObjectAddress informationObjectAddress, final Value<?> value );
-
-    public void dispose ();
-}
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/DataModelImpl.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/DataModelImpl.java
index da14585..c36b86b 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/DataModelImpl.java
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/DataModelImpl.java
@@ -7,42 +7,29 @@
  *
  * Contributors:
  *     IBH SYSTEMS GmbH - initial API and implementation
+ *     Red Hat Inc - refactor and allow re-use outside of exporter
  *******************************************************************************/
 package org.eclipse.neoscada.da.server.exporter.iec60870;
 
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
 
-import org.eclipse.neoscada.protocol.iec60870.asdu.ASDUHeader;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.ASDUAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.CauseOfTransmission;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationEntry;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationObjectAddress;
 import org.eclipse.neoscada.protocol.iec60870.asdu.types.QualityInformation;
 import org.eclipse.neoscada.protocol.iec60870.asdu.types.Value;
-import org.eclipse.neoscada.protocol.iec60870.io.MirrorCommand;
-import org.eclipse.neoscada.protocol.iec60870.server.data.AbstractBaseDataModel;
-import org.eclipse.neoscada.protocol.iec60870.server.data.BackgroundIterator;
 import org.eclipse.neoscada.protocol.iec60870.server.data.DataListener;
 import org.eclipse.neoscada.protocol.iec60870.server.data.DefaultSubscription;
+import org.eclipse.neoscada.protocol.iec60870.server.data.Stopping;
 import org.eclipse.neoscada.protocol.iec60870.server.data.Subscription;
-import org.eclipse.neoscada.protocol.iec60870.server.data.event.MessageBuilder;
-import org.eclipse.neoscada.protocol.iec60870.server.data.event.SimpleBooleanBuilder;
-import org.eclipse.neoscada.protocol.iec60870.server.data.event.SimpleFloatBuilder;
+import org.eclipse.neoscada.protocol.iec60870.server.data.model.BackgroundModel;
+import org.eclipse.neoscada.protocol.iec60870.server.data.model.ChangeDataModel;
+import org.eclipse.neoscada.protocol.iec60870.server.data.model.ChangeModel;
+import org.eclipse.neoscada.protocol.iec60870.server.data.model.WriteModel;
+import org.eclipse.neoscada.protocol.iec60870.server.data.model.WriteModel.Action;
 import org.eclipse.scada.core.NotConvertableException;
 import org.eclipse.scada.core.NullValueException;
 import org.eclipse.scada.core.Variant;
@@ -50,13 +37,10 @@
 import org.eclipse.scada.da.core.WriteResult;
 import org.eclipse.scada.da.server.exporter.common.HiveSource;
 import org.eclipse.scada.da.server.exporter.common.SingleSubscriptionManager;
-import org.eclipse.scada.da.server.exporter.common.SingleSubscriptionManager.Listener;
-import org.eclipse.scada.utils.concurrent.NamedThreadFactory;
 import org.eclipse.scada.utils.concurrent.NotifyFuture;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
 import com.google.common.util.concurrent.ListenableFuture;
 
 /**
@@ -65,33 +49,11 @@
  * <p>
  * All entries are initialized with {@link DataItemValue#DISCONNECTED} in order
  * to populate the caches. This will allow the background transfer to already
- * have data before changes came in. And also prevent the cache structures to
- * change, so the background transmission may hold the iterators.
+ * have data before changes came in.
  * </p>
  */
-public class DataModelImpl extends AbstractBaseDataModel
+public class DataModelImpl extends ChangeDataModel
 {
-    private static final SimpleFloatBuilder FLOAT_BUILDER = new SimpleFloatBuilder ( false );
-
-    private static final SimpleBooleanBuilder BOOLEAN_BUILDER = new SimpleBooleanBuilder ( false );
-
-    private final static class BackgroundState
-    {
-        public Entry<Integer, SortedMap<Integer, Value<?>>> asduAddress;
-
-        public Iterator<Entry<Integer, SortedMap<Integer, Value<?>>>> asduAddressIterator;
-
-        public Iterator<Entry<Integer, Value<?>>> addressIterator;
-
-        public SortedMap<Integer, Value<?>> addressMap;
-
-        @Override
-        public String toString ()
-        {
-            return String.format ( "[asduAddress: %s, asduAddressIterator: %s, addressMap: %s, addressIterator: %s]", this.asduAddress, this.asduAddressIterator, this.addressMap, this.addressIterator );
-        }
-    }
-
     private final static class AddressKey
     {
         private final int asduAddress;
@@ -143,91 +105,80 @@
 
     }
 
-    private final class BackgroundIteratorImpl implements BackgroundIterator
-    {
-        private final BackgroundState state = new BackgroundState ();
-
-        @Override
-        public Object nextMessage ()
-        {
-            return proceedBackgroundScan ( this.state );
-        }
-    }
-
     private final static Logger logger = LoggerFactory.getLogger ( DataModelImpl.class );
 
+    private final Long flushDelay;
+
+    private final boolean supportBackgroundScan;
+
     private final SingleSubscriptionManager manager;
 
-    /*
-     * we use a sorted map, so that the addresses are sorted and we
-     * can generate continuous addresses in the message builders
-     */
-    private final Map<Integer, SortedMap<Integer, Value<?>>> cache;
-
     private final InformationBean info;
 
-    private final ExecutorService backgroundExecutor;
-
     private final Map<AddressKey, String> addressMap = new HashMap<> ();
 
-    private final ChangeModel changeModel;
-
-    public DataModelImpl ( final HiveSource hiveSource, final Set<MappingEntry> entries, final Properties hiveProperties, final InformationBean info, final Long flushDelay )
+    public DataModelImpl ( final HiveSource hiveSource, final Set<MappingEntry> entries, final Properties hiveProperties, final InformationBean info, final Long flushDelay, final boolean supportBackgroundScan )
     {
         super ( "org.eclipse.neoscada.da.server.exporter.iec60870.DataModel" );
 
-        this.backgroundExecutor = Executors.newSingleThreadExecutor ( new NamedThreadFactory ( "org.eclipse.neoscada.da.server.exporter.iec60870.DataModel/background" ) );
-        this.changeModel = flushDelay == null ? makeInstantChangeModel () : makeBufferingChangeModel ( flushDelay );
+        this.flushDelay = flushDelay;
+        this.supportBackgroundScan = supportBackgroundScan;
 
         this.info = info;
 
         for ( final MappingEntry entry : entries )
         {
-            this.addressMap.put ( new AddressKey ( entry.getAsduAddress (), entry.getAddress () ), entry.getItemId () );
+            this.addressMap.put ( new AddressKey ( entry.getAsduAddress ().getAddress (), entry.getAddress ().getAddress () ), entry.getItemId () );
         }
 
-        this.cache = new HashMap<> ();
-
         this.manager = new SingleSubscriptionManager ( this.executor, hiveSource, hiveProperties, "IEC60870/DataModel" );
         this.manager.start ();
 
         attach ( entries );
     }
 
-    private ChangeModel makeInstantChangeModel ()
+    @Override
+    protected BackgroundModel createBackgroundModel ()
     {
-        return new InstantChangeModel ( new InstantChangeModel.Context () {
-
-            @Override
-            public void notifyChangeFloat ( final ASDUAddress asduAddress, final InformationObjectAddress startAddress, final List<Value<Float>> values )
-            {
-                DataModelImpl.this.notifyChangeFloat ( asduAddress, startAddress, values );
-            }
-
-            @Override
-            public void notifyChangeBoolean ( final ASDUAddress asduAddress, final InformationObjectAddress startAddress, final List<Value<Boolean>> values )
-            {
-                DataModelImpl.this.notifyChangeBoolean ( asduAddress, startAddress, values );
-            }
-        } );
+        if ( this.supportBackgroundScan )
+        {
+            return makeDefaultBackgroundModel ();
+        }
+        else
+        {
+            return BackgroundModel.NONE;
+        }
     }
 
-    private ChangeModel makeBufferingChangeModel ( final long flushDelay )
+    @Override
+    protected ChangeModel createChangeModel ()
     {
-        return new BufferingChangeModel ( new BufferingChangeModel.Context () {
+        return this.flushDelay == null ? makeInstantChangeModel () : makeBufferingChangeModel ( this.flushDelay );
+    }
+
+    @Override
+    protected WriteModel createWriteModel ()
+    {
+        return new WriteModel () {
 
             @Override
-            public void notifyBoolean ( final ASDUAddress asduAddress, final List<InformationEntry<Boolean>> values )
+            public Action prepareCommand ( final Request<Boolean> request )
             {
-                DataModelImpl.this.notifyChangeBoolean ( asduAddress, values );
+                return DataModelImpl.this.prepareCommand ( request );
             }
 
             @Override
-            public void notifyFloat ( final ASDUAddress asduAddress, final List<InformationEntry<Float>> values )
+            public Action prepareSetpointFloat ( final Request<Float> request )
             {
-                DataModelImpl.this.notifyChangeFloat ( asduAddress, values );
+                return DataModelImpl.this.prepareSetpointFloat ( request );
             }
-        }, this.executor, flushDelay );
+
+            @Override
+            public Action prepareSetpointScaled ( final Request<Short> request )
+            {
+                return DataModelImpl.this.prepareSetpointScaled ( request );
+            }
+        };
     }
 
     @Override
@@ -252,14 +203,7 @@
         {
             logger.debug ( "Attaching to: {}", entry );
             handleStateChanged ( entry, DataItemValue.DISCONNECTED, false );
-            this.manager.addListener ( entry.getItemId (), new Listener () {
-
-                @Override
-                public void stateChanged ( final String itemId, final DataItemValue value )
-                {
-                    handleStateChanged ( entry, value, true );
-                }
-            } );
+            this.manager.addListener ( entry.getItemId (), ( itemId, value ) -> handleStateChanged ( entry, value, true ) );
         }
     }
 
@@ -267,21 +211,13 @@
     {
         logger.trace ( "Handle state change - entry: {}, value: {}", entry, value );
 
-        SortedMap<Integer, Value<?>> unit = this.cache.get ( entry.getAsduAddress () );
-        if ( unit == null )
-        {
-            unit = new TreeMap<> ();
-            this.cache.put ( entry.getAsduAddress (), unit );
-        }
-
         final Value<?> iecValue = convert ( entry, value );
         logger.trace ( "Converted to: {}", iecValue );
-        unit.put ( entry.getAddress (), iecValue );
 
-        notifyChange ( entry, iecValue );
+        notifyDataChange ( entry.getAsduAddress (), entry.getAddress (), iecValue, notify );
     }
 
-    private Value<?> convert ( final MappingEntry entry, final DataItemValue value )
+    private static Value<?> convert ( final MappingEntry entry, final DataItemValue value )
     {
         Variant v = value.getValue ();
         if ( v == null )
@@ -306,7 +242,7 @@
             {
                 return errorValue ( entry );
             }
-            return new Value<Object> ( cv, convertTimestamp ( value ), convertQuality ( value ) );
+            return new Value<> ( cv, convertTimestamp ( value ), convertQuality ( value ) );
         }
         catch ( final Exception e )
         {
@@ -315,7 +251,7 @@
         }
     }
 
-    private long convertTimestamp ( final DataItemValue value )
+    private static long convertTimestamp ( final DataItemValue value )
     {
         if ( value == null )
         {
@@ -329,7 +265,7 @@
         return ts.asLong ( System.currentTimeMillis () );
     }
 
-    private Object convertValue ( final MappingEntry entry, final DataItemValue value ) throws NullValueException, NotConvertableException
+    private static Object convertValue ( final MappingEntry entry, final DataItemValue value ) throws NullValueException, NotConvertableException
     {
         Variant v = value.getValue ();
 
@@ -367,7 +303,7 @@
         }
     }
 
-    private QualityInformation convertQuality ( final DataItemValue value )
+    private static QualityInformation convertQuality ( final DataItemValue value )
     {
         final boolean valid = value.isConnected () && !value.isError ();
         final boolean substituted = value.isManual ();
@@ -376,363 +312,72 @@
         return new QualityInformation ( blocked, substituted, topical, valid );
     }
 
-    private Value<?> errorValue ( final MappingEntry entry )
+    private static Value<?> errorValue ( final MappingEntry entry )
     {
         switch ( entry.getValueType () )
         {
             case FLOAT:
-                return new Value<Float> ( 0.0f, System.currentTimeMillis (), QualityInformation.INVALID );
+                return new Value<> ( 0.0f, System.currentTimeMillis (), QualityInformation.INVALID );
             case BOOLEAN:
             default:
-                return new Value<Boolean> ( false, System.currentTimeMillis (), QualityInformation.INVALID );
+                return new Value<> ( false, System.currentTimeMillis (), QualityInformation.INVALID );
         }
     }
 
     @Override
-    public void dispose ()
+    public Stopping stop ()
     {
-        this.changeModel.dispose ();
         this.manager.stop ();
-        this.backgroundExecutor.shutdown ();
-        super.dispose ();
+        return super.stop ();
     }
 
-    @Override
-    public void disposeAndWait () throws InterruptedException
+    protected Action prepareCommand ( final WriteModel.Request<Boolean> request )
     {
-        super.disposeAndWait ();
-        this.backgroundExecutor.awaitTermination ( Long.MAX_VALUE, TimeUnit.MILLISECONDS );
+        return perpareWrite ( request, Variant.valueOf ( request.getValue () ) );
     }
 
-    @Override
-    public ListenableFuture<Value<?>> read ( final ASDUAddress asduAddress, final InformationObjectAddress address )
+    protected Action prepareSetpointFloat ( final WriteModel.Request<Float> request )
     {
-        return this.executor.submit ( new Callable<Value<?>> () {
-
-            @Override
-            public Value<?> call () throws Exception
-            {
-                return performRead ( asduAddress, address );
-            }
-        } );
+        return perpareWrite ( request, Variant.valueOf ( request.getValue () ) );
     }
 
-    protected synchronized Value<?> performRead ( final ASDUAddress asduAddress, final InformationObjectAddress address )
+    protected Action prepareSetpointScaled ( final WriteModel.Request<Short> request )
     {
-        final Map<Integer, Value<?>> map = this.cache.get ( asduAddress.getAddress () );
-        if ( map == null )
-        {
-            return null;
-        }
-
-        return map.get ( address.getAddress () );
+        return perpareWrite ( request, Variant.valueOf ( request.getValue () ) );
     }
 
-    @Override
-    public synchronized ListenableFuture<Void> readAll ( final ASDUAddress asduAddress, final Runnable prepare, final DataListener listener )
+    private synchronized Action perpareWrite ( final WriteModel.Request<?> request, final Variant value )
     {
-        final Map<Integer, Value<?>> map = this.cache.get ( asduAddress.getAddress () );
-        if ( map == null )
-        {
-            return null;
-        }
+        logger.debug ( "Request to write - request: {}, value: {}", request, value );
 
-        final Map<Integer, Value<?>> map2 = new HashMap<> ( map ); // copy
-
-        this.executor.submit ( prepare );
-
-        return this.executor.submit ( new Callable<Void> () {
-
-            @Override
-            public Void call ()
-            {
-                performReadAll ( asduAddress, listener, map2 );
-                return null;
-            }
-        } );
-    }
-
-    protected synchronized void performReadAll ( final ASDUAddress asduAddress, final DataListener listener, final Map<Integer, Value<?>> map )
-    {
-        for ( final Map.Entry<Integer, Value<?>> entry : map.entrySet () )
-        {
-            fireListener ( asduAddress, listener, entry );
-        }
-    }
-
-    @Override
-    public void forAllAsdu ( final Function<ASDUAddress, Void> function, final Runnable ifNoneFound )
-    {
-        this.executor.execute ( new Runnable () {
-
-            @Override
-            public void run ()
-            {
-                performForAllAsdu ( function, ifNoneFound );
-            }
-        } );
-    }
-
-    protected synchronized void performForAllAsdu ( final Function<ASDUAddress, Void> function, final Runnable ifNoneFound )
-    {
-        if ( this.cache.isEmpty () )
-        {
-            this.executor.execute ( ifNoneFound );
-            return;
-        }
-
-        for ( final Integer asdu : this.cache.keySet () )
-        {
-            this.executor.execute ( new Runnable () {
-
-                @Override
-                public void run ()
-                {
-                    function.apply ( ASDUAddress.valueOf ( asdu ) );
-                }
-            } );
-        }
-    }
-
-    @Override
-    public BackgroundIterator createBackgroundIterator ()
-    {
-        return new BackgroundIteratorImpl ();
-    }
-
-    public Object proceedBackgroundScan ( final BackgroundState state )
-    {
-        try
-        {
-            logger.debug ( "Background scan - {}", state );
-
-            final Object msg = this.backgroundExecutor.submit ( new Callable<Object> () {
-                @Override
-                public Object call () throws Exception
-                {
-                    return internalBackgroundScan ( state );
-                }
-            } ).get ();
-            logger.debug ( "Background scan result - {}", msg );
-            return msg;
-        }
-        catch ( InterruptedException | ExecutionException e )
-        {
-            throw new RuntimeException ( "Failed to perform background scan", e );
-        }
-    }
-
-    @SuppressWarnings ( { "unchecked", "rawtypes" } )
-    protected synchronized Object internalBackgroundScan ( final BackgroundState state )
-    {
-        if ( state.asduAddressIterator == null )
-        {
-            state.asduAddressIterator = this.cache.entrySet ().iterator ();
-        }
-        if ( state.asduAddress == null )
-        {
-            if ( !state.asduAddressIterator.hasNext () )
-            {
-                return null;
-            }
-            else
-            {
-                state.asduAddress = state.asduAddressIterator.next ();
-            }
-        }
-
-        if ( state.addressIterator == null )
-        {
-            state.addressMap = state.asduAddress.getValue ();
-            state.addressIterator = state.addressMap.entrySet ().iterator ();
-        }
-
-        MessageBuilder<?, ?> builder = null;
-        do
-        {
-            if ( !state.addressIterator.hasNext () )
-            {
-                // end of entries for current common asdu address
-                state.addressIterator = null; // reset address iterator
-                state.asduAddress = null;
-
-                if ( builder != null )
-                {
-                    return builder.build (); // end this message
-                }
-                else
-                {
-                    return null; // we never had content
-                }
-            }
-
-            final Entry<Integer, Value<?>> entry = state.addressIterator.next ();
-
-            if ( builder == null )
-            {
-                builder = createBuilder ( entry.getValue ().getValue () );
-                builder.start ( CauseOfTransmission.BACKGROUND, ASDUAddress.valueOf ( state.asduAddress.getKey () ) );
-            }
-
-            if ( !builder.accepts ( entry.getValue () ) )
-            {
-                // next starting point
-                state.addressMap = state.asduAddress.getValue ().tailMap ( entry.getKey () );
-                state.addressIterator = state.addressMap.entrySet ().iterator ();
-                // stop right now
-                return builder.build ();
-            }
-            else
-            {
-                if ( !builder.addEntry ( InformationObjectAddress.valueOf ( entry.getKey () ), (Value)entry.getValue () ) )
-                {
-                    // builder is full
-                    return builder.build ();
-                }
-            }
-        } while ( true );
-    }
-
-    private void notifyChange ( final MappingEntry entry, final Value<?> value )
-    {
-        this.changeModel.notifyChange ( ASDUAddress.valueOf ( entry.getAsduAddress () ), new InformationObjectAddress ( entry.getAddress () ), value );
-    }
-
-    @SuppressWarnings ( "unchecked" )
-    private static void fireListener ( final ASDUAddress asduAddress, final DataListener listener, final Map.Entry<Integer, Value<?>> entry )
-    {
-        final Value<?> ve = entry.getValue ();
-        final Object v = ve.getValue ();
-
-        if ( v instanceof Boolean )
-        {
-            listener.dataChangeBoolean ( asduAddress, InformationObjectAddress.valueOf ( entry.getKey () ), Collections.singletonList ( (Value<Boolean>)ve ) );
-        }
-        else if ( v instanceof Float )
-        {
-            listener.dataChangeFloat ( asduAddress, InformationObjectAddress.valueOf ( entry.getKey () ), Collections.singletonList ( (Value<Float>)ve ) );
-        }
-    }
-
-    private static MessageBuilder<?, ?> createBuilder ( final Object value )
-    {
-        if ( value instanceof Boolean )
-        {
-            return BOOLEAN_BUILDER.create ();
-        }
-        else if ( value instanceof Number )
-        {
-            return FLOAT_BUILDER.create ();
-        }
-        throw new IllegalStateException ( String.format ( "Value type %s is unsupported", value.getClass () ) );
-    }
-
-    private static class WriteRequest
-    {
-        private final ASDUAddress asduAddress;
-
-        private final InformationObjectAddress address;
-
-        private final Variant value;
-
-        public WriteRequest ( final ASDUAddress asduAddress, final InformationObjectAddress address, final Variant value )
-        {
-            this.asduAddress = asduAddress;
-            this.address = address;
-            this.value = value;
-        }
-
-        public InformationObjectAddress getAddress ()
-        {
-            return this.address;
-        }
-
-        public ASDUAddress getAsduAddress ()
-        {
-            return this.asduAddress;
-        }
-
-        public Variant getValue ()
-        {
-            return this.value;
-        }
-
-        @Override
-        public String toString ()
-        {
-            return String.format ( "[WriteRequest - asduAddress: %s, objectAddress: %s, value: %s", this.asduAddress, this.address, this.value );
-        }
-    }
-
-    private WriteRequest convert ( final ASDUHeader header, final InformationObjectAddress informationObjectAddress, final Variant value )
-    {
-        return new WriteRequest ( header.getAsduAddress (), informationObjectAddress, value );
-    }
-
-    @Override
-    public void writeCommand ( final ASDUHeader header, final InformationObjectAddress informationObjectAddress, final boolean state, final byte type, final MirrorCommand mirrorCommand, final boolean execute )
-    {
-        performWrite ( header, informationObjectAddress, Variant.valueOf ( state ), mirrorCommand, execute );
-    }
-
-    @Override
-    public void writeValue ( final ASDUHeader header, final InformationObjectAddress informationObjectAddress, final float value, final byte type, final MirrorCommand mirrorCommand, final boolean execute )
-    {
-        performWrite ( header, informationObjectAddress, Variant.valueOf ( value ), mirrorCommand, execute );
-    }
-
-    @Override
-    public void writeScaledValue ( final ASDUHeader header, final InformationObjectAddress informationObjectAddress, final short value, final byte type, final MirrorCommand mirrorCommand, final boolean execute )
-    {
-        performWrite ( header, informationObjectAddress, Variant.valueOf ( value ), mirrorCommand, execute );
-    }
-
-    private void performWrite ( final ASDUHeader header, final InformationObjectAddress informationObjectAddress, final Variant value, final MirrorCommand mirrorCommand, final boolean execute )
-    {
-        final WriteRequest request = convert ( header, informationObjectAddress, value );
-        this.executor.execute ( new Runnable () {
-
-            @Override
-            public void run ()
-            {
-                if ( !performWrite ( request, mirrorCommand, execute ) )
-                {
-                    mirrorCommand.sendActivationConfirm ( false );
-                }
-                // actterm will be sent be the write method itself
-            }
-        } );
-    }
-
-    protected synchronized boolean performWrite ( final WriteRequest request, final MirrorCommand mirrorCommand, final boolean execute )
-    {
-        logger.debug ( "Request to write - request: {}, execute: {}", request, execute );
-
-        final String itemId = this.addressMap.get ( new AddressKey ( request.getAsduAddress ().getAddress (), request.getAddress ().getAddress () ) );
+        final String itemId = this.addressMap.get ( new AddressKey ( request.getHeader ().getAsduAddress ().getAddress (), request.getAddress ().getAddress () ) );
         if ( itemId == null )
         {
             logger.info ( "Item for request not found - request: {}", request );
-            return false;
+            return null;
         }
 
         logger.debug ( "Request to write to item: {}", itemId );
 
-        mirrorCommand.sendActivationConfirm ( true );
-
-        if ( execute )
+        if ( !request.isExecute () )
         {
-            final NotifyFuture<WriteResult> future = this.manager.writeValue ( itemId, request.getValue (), null, null );
-            future.addListener ( new Runnable () {
+            return () -> CompletableFuture.completedFuture ( null );
+        }
+        else
+        {
+            return new Action () {
 
                 @Override
-                public void run ()
+                public CompletionStage<Void> execute ()
                 {
-                    logger.debug ( "Write command completed - request: {}, item: {}", request, itemId );
-                    mirrorCommand.sendActivationTermination ();
-                }
-            } );
-        }
+                    final NotifyFuture<WriteResult> future = DataModelImpl.this.manager.writeValue ( itemId, value, null, null );
 
-        return true;
+                    final CompletableFuture<Void> result = new CompletableFuture<> ();
+                    future.addListener ( () -> result.complete ( null ) );
+
+                    return result;
+                }
+            };
+        }
     }
 }
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ExportConfiguration.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ExportConfiguration.java
index 1cfc096..8d0a584 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ExportConfiguration.java
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/ExportConfiguration.java
@@ -18,6 +18,8 @@
 import org.eclipse.neoscada.core.common.iec60870.Configurations;
 import org.eclipse.neoscada.da.server.exporter.iec60870.MappingEntry.ValueType;
 import org.eclipse.neoscada.protocol.iec60870.ProtocolOptions;
+import org.eclipse.neoscada.protocol.iec60870.asdu.types.ASDUAddress;
+import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationObjectAddress;
 import org.eclipse.neoscada.protocol.iec60870.server.data.DataModuleOptions;
 import org.eclipse.scada.ca.ConfigurationDataHelper;
 
@@ -168,7 +170,7 @@
             itemId = item[0];
         }
 
-        return new MappingEntry ( itemId, asduAddress, infoAddress, type );
+        return new MappingEntry ( itemId, ASDUAddress.valueOf ( asduAddress ), InformationObjectAddress.valueOf ( infoAddress ), type );
     }
 
     private static int convertAddress ( final String string )
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/IecExport.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/IecExport.java
index 4ac1783..a43c400 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/IecExport.java
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/IecExport.java
@@ -95,10 +95,12 @@
 
         if ( this.configuration != null )
         {
-            this.dataModule = new DataModule ( this.configuration.getDataModuleOptions (), new DataModelImpl ( this.hiveSource, this.configuration.getEntries (), this.configuration.getHiveProperties (), this.info, this.configuration.getSpontaneousBufferWindow () ) );
+            final DataModelImpl dataModel = new DataModelImpl ( this.hiveSource, this.configuration.getEntries (), this.configuration.getHiveProperties (), this.info, this.configuration.getSpontaneousBufferWindow (), true );
+            dataModel.start ();
+            this.dataModule = new DataModule ( this.configuration.getDataModuleOptions (), dataModel );
             this.info.setItems ( this.configuration.getEntries ().size () );
 
-            final List<ServerModule> modules = new LinkedList<ServerModule> ();
+            final List<ServerModule> modules = new LinkedList<> ();
             modules.add ( this.dataModule );
 
             this.server = new Server ( this.configuration.getPort (), this.configuration.getProtocolOptions (), modules );
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/InstantChangeModel.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/InstantChangeModel.java
deleted file mode 100644
index 442d7d5..0000000
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/InstantChangeModel.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 IBH SYSTEMS GmbH and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *     IBH SYSTEMS GmbH - initial API and implementation
- *******************************************************************************/
-package org.eclipse.neoscada.da.server.exporter.iec60870;
-
-import static java.util.Collections.singletonList;
-
-import java.util.List;
-
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.ASDUAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationObjectAddress;
-import org.eclipse.neoscada.protocol.iec60870.asdu.types.Value;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class InstantChangeModel implements ChangeModel
-{
-    public interface Context
-    {
-        public void notifyChangeBoolean ( ASDUAddress asduAddress, InformationObjectAddress startAddress, List<Value<Boolean>> values );
-
-        public void notifyChangeFloat ( ASDUAddress asduAddress, InformationObjectAddress startAddress, List<Value<Float>> values );
-    }
-
-    private final static Logger logger = LoggerFactory.getLogger ( InstantChangeModel.class );
-
-    private final Context context;
-
-    public InstantChangeModel ( final Context context )
-    {
-        this.context = context;
-    }
-
-    @Override
-    public void dispose ()
-    {
-    }
-
-    @SuppressWarnings ( "unchecked" )
-    @Override
-    public void notifyChange ( final ASDUAddress asduAddress, final InformationObjectAddress informationObjectAddress, final Value<?> iecValue )
-    {
-        final Object rawValue = iecValue.getValue ();
-
-        logger.trace ( "Notify raw value: {} ({})", rawValue, rawValue != null ? rawValue.getClass () : null );
-
-        if ( rawValue instanceof Boolean )
-        {
-            this.context.notifyChangeBoolean ( asduAddress, informationObjectAddress, singletonList ( (Value<Boolean>)iecValue ) );
-        }
-        else if ( rawValue instanceof Float )
-        {
-            this.context.notifyChangeFloat ( asduAddress, informationObjectAddress, singletonList ( (Value<Float>)iecValue ) );
-        }
-    }
-
-}
diff --git a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/MappingEntry.java b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/MappingEntry.java
index 1a49a0b..9565efc 100644
--- a/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/MappingEntry.java
+++ b/org.eclipse.neoscada.da.server.exporter.iec60870/src/org/eclipse/neoscada/da/server/exporter/iec60870/MappingEntry.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2014 IBH SYSTEMS GmbH and others.
+ * Copyright (c) 2014, 2016 IBH SYSTEMS GmbH and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,9 +7,13 @@
  *
  * Contributors:
  *     IBH SYSTEMS GmbH - initial API and implementation
+ *     Red Hat Inc - refactor data model
  *******************************************************************************/
 package org.eclipse.neoscada.da.server.exporter.iec60870;
 
+import org.eclipse.neoscada.protocol.iec60870.asdu.types.ASDUAddress;
+import org.eclipse.neoscada.protocol.iec60870.asdu.types.InformationObjectAddress;
+
 public class MappingEntry
 {
     public static enum ValueType
@@ -20,13 +24,13 @@
 
     private final String itemId;
 
-    private final int asduAddress;
+    private final ASDUAddress asduAddress;
 
-    private final int address;
+    private final InformationObjectAddress address;
 
     private final ValueType valueType;
 
-    public MappingEntry ( final String itemId, final int asduAddress, final int address, final ValueType valueType )
+    public MappingEntry ( final String itemId, final ASDUAddress asduAddress, final InformationObjectAddress address, final ValueType valueType )
     {
         this.itemId = itemId;
         this.asduAddress = asduAddress;
@@ -39,16 +43,16 @@
         return this.itemId;
     }
 
-    public int getAddress ()
-    {
-        return this.address;
-    }
-
-    public int getAsduAddress ()
+    public ASDUAddress getAsduAddress ()
     {
         return this.asduAddress;
     }
 
+    public InformationObjectAddress getAddress ()
+    {
+        return this.address;
+    }
+
     public ValueType getValueType ()
     {
         return this.valueType;
@@ -65,8 +69,8 @@
     {
         final int prime = 31;
         int result = 1;
-        result = prime * result + this.address;
-        result = prime * result + this.asduAddress;
+        result = prime * result + ( this.address == null ? 0 : this.address.hashCode () );
+        result = prime * result + ( this.asduAddress == null ? 0 : this.asduAddress.hashCode () );
         result = prime * result + ( this.itemId == null ? 0 : this.itemId.hashCode () );
         result = prime * result + ( this.valueType == null ? 0 : this.valueType.hashCode () );
         return result;
@@ -88,11 +92,25 @@
             return false;
         }
         final MappingEntry other = (MappingEntry)obj;
-        if ( this.address != other.address )
+        if ( this.address == null )
+        {
+            if ( other.address != null )
+            {
+                return false;
+            }
+        }
+        else if ( !this.address.equals ( other.address ) )
         {
             return false;
         }
-        if ( this.asduAddress != other.asduAddress )
+        if ( this.asduAddress == null )
+        {
+            if ( other.asduAddress != null )
+            {
+                return false;
+            }
+        }
+        else if ( !this.asduAddress.equals ( other.asduAddress ) )
         {
             return false;
         }
diff --git a/org.eclipse.neoscada.da.server.iec60870/META-INF/MANIFEST.MF b/org.eclipse.neoscada.da.server.iec60870/META-INF/MANIFEST.MF
index fbc6580..8a699e5 100644
--- a/org.eclipse.neoscada.da.server.iec60870/META-INF/MANIFEST.MF
+++ b/org.eclipse.neoscada.da.server.iec60870/META-INF/MANIFEST.MF
@@ -6,13 +6,13 @@
 Bundle-Vendor: Eclipse NeoSCADA
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Import-Package: com.google.common.util.concurrent;version="15.0.0",
- org.eclipse.neoscada.core.common.iec60870;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.asdu;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.asdu.message;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.asdu.types;version="1.3.1",
- org.eclipse.neoscada.protocol.iec60870.client;version="1.3.0",
- org.eclipse.neoscada.protocol.iec60870.client.data;version="1.3.0",
+ org.eclipse.neoscada.core.common.iec60870;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.asdu;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.asdu.message;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.asdu.types;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.client;version="0.4.0",
+ org.eclipse.neoscada.protocol.iec60870.client.data;version="0.4.0",
  org.eclipse.scada.ca;version="0.2.0",
  org.eclipse.scada.ca.common.factory;version="0.2.0",
  org.eclipse.scada.core;version="0.1.0",
@@ -43,3 +43,4 @@
  org.osgi.framework;version="1.7.0",
  org.slf4j;version="1.7.2"
 Service-Component: OSGI-INF/hive.xml
+Bundle-ActivationPolicy: lazy