Implemented functionality for multiple seed nodes for network maps; Network map seed...
authorEriks Jenkevics <eriks@netxms.org>
Thu, 6 Apr 2017 09:48:37 +0000 (12:48 +0300)
committerEriks Jenkevics <eriks@netxms.org>
Wed, 12 Apr 2017 14:12:03 +0000 (17:12 +0300)
20 files changed:
include/netxms_maps.h
include/netxmsdb.h
include/nms_cscp.h
sql/schema.in
src/java/client/netxms-base/src/main/java/org/netxms/base/NXCPCodes.java
src/java/client/netxms-client/src/main/java/org/netxms/client/NXCObjectCreationData.java
src/java/client/netxms-client/src/main/java/org/netxms/client/NXCObjectModificationData.java
src/java/client/netxms-client/src/main/java/org/netxms/client/NXCSession.java
src/java/client/netxms-client/src/main/java/org/netxms/client/objects/NetworkMap.java
src/java/netxms-eclipse/NetworkMaps/src/org/netxms/ui/eclipse/networkmaps/dialogs/CreateNetworkMapDialog.java
src/java/netxms-eclipse/ObjectManager/plugin.xml
src/java/netxms-eclipse/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/propertypages/MapSeedNodes.java [new file with mode: 0644]
src/libnxmap/link.cpp
src/server/core/netmap.cpp
src/server/core/session.cpp
src/server/include/nms_objects.h
src/server/tools/nxdbmgr/upgrade.cpp
webui/webapp/NetworkMaps/src/org/netxms/ui/eclipse/networkmaps/dialogs/CreateNetworkMapDialog.java
webui/webapp/ObjectManager/plugin.xml
webui/webapp/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/propertypages/MapSeedNodes.java [new file with mode: 0644]

index 68cdd92..eebfff2 100644 (file)
@@ -305,6 +305,7 @@ public:
        void setConnector2Name(const TCHAR *name);
        void setFlags(UINT32 flags) { m_flags = flags; }
        void setConfig(const TCHAR *name);
+       void swap();
 };
 
 
index 123991b..c7cd4a8 100644 (file)
@@ -23,6 +23,6 @@
 #ifndef _netxmsdb_h
 #define _netxmsdb_h
 
-#define DB_FORMAT_VERSION   445
+#define DB_FORMAT_VERSION   446
 
 #endif
index bb7a5ea..119b128 100644 (file)
@@ -957,7 +957,7 @@ typedef struct
 #define VID_FILE_SIZE               ((UINT32)356)
 #define VID_MAP_TYPE                ((UINT32)357)
 #define VID_LAYOUT                  ((UINT32)358)
-#define VID_SEED_OBJECT             ((UINT32)359)
+#define VID_SEED_OBJECTS            ((UINT32)359)
 #define VID_BACKGROUND              ((UINT32)360)
 #define VID_NUM_ELEMENTS            ((UINT32)361)
 #define VID_INTERFACE_ID            ((UINT32)362)
index 0e03773..f61923b 100644 (file)
@@ -1337,7 +1337,6 @@ CREATE TABLE network_maps
   id integer not null,
   map_type integer not null,
   layout integer not null,
-  seed integer not null,
   radius integer not null,
   background varchar(36) null,
   bg_latitude varchar(20) null,
@@ -1384,6 +1383,16 @@ CREATE TABLE network_map_links
 CREATE INDEX idx_network_map_links_map_id ON network_map_links(map_id);
 
 /**
+ * Seed nodes of network maps
+ */
+CREATE TABLE network_map_seed_nodes
+(
+  map_id integer not null,
+  seed_node_id integer not null,
+  PRIMARY KEY(map_id,seed_node_id)
+) TABLE_TYPE;
+
+/**
  * Image Library
  */
 CREATE TABLE images
index 3f5fc13..3986777 100644 (file)
@@ -742,7 +742,7 @@ public class NXCPCodes
        public static final long VID_FILE_SIZE = 356;
        public static final long VID_MAP_TYPE = 357;
        public static final long VID_LAYOUT = 358;
-       public static final long VID_SEED_OBJECT = 359;
+       public static final long VID_SEED_OBJECTS = 359;
        public static final long VID_BACKGROUND = 360;
        public static final long VID_NUM_ELEMENTS = 361;
        public static final long VID_INTERFACE_ID = 362;
index a202d58..1f586c5 100644 (file)
@@ -20,6 +20,8 @@ package org.netxms.client;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
 import org.netxms.base.InetAddressEx;
 import org.netxms.client.objects.AbstractObject;
 import org.netxms.client.objects.NetworkService;
@@ -49,7 +51,7 @@ public class NXCObjectCreationData
    private long icmpProxyId;
    private long sshProxyId;
        private int mapType;
-       private long seedObjectId;
+       private List<Long> seedObjectIds;
        private long zoneId;
        private int serviceType;
        private int ipProtocol;
@@ -105,7 +107,7 @@ public class NXCObjectCreationData
                icmpProxyId = 0;
                sshProxyId = 0;
                mapType = 0;
-               seedObjectId = 0;
+               seedObjectIds = new ArrayList<Long>();
                zoneId = 0;
                serviceType = NetworkService.CUSTOM;
                ipProtocol = 6;
@@ -302,11 +304,11 @@ public class NXCObjectCreationData
        }
 
        /**
-        * @return the seedObjectId
+        * @return the seedObjectIds
         */
-       public long getSeedObjectId()
+       public Long[] getSeedObjectIds()
        {
-               return seedObjectId;
+               return seedObjectIds.toArray(new Long[seedObjectIds.size()]);
        }
 
        /**
@@ -314,7 +316,16 @@ public class NXCObjectCreationData
         */
        public void setSeedObjectId(long seedObjectId)
        {
-               this.seedObjectId = seedObjectId;
+               seedObjectIds.clear();
+               seedObjectIds.add(seedObjectId);
+       }
+       
+       /**
+        * @param seedObjectIds the seed node object Ids to set
+        */
+       public void setSeedObjectIds(List<Long> seedObjectIds)
+       {
+          this.seedObjectIds = seedObjectIds;
        }
 
        /**
index b30e8c8..fd3e8c1 100644 (file)
@@ -118,6 +118,7 @@ public class NXCObjectModificationData
    public static final int ZONE_PROXY             = 67;
    public static final int AGENT_COMPRESSION_MODE = 68;
    public static final int URL_LIST               = 69;
+   public static final int SEED_OBJECTS           = 70;
        
        private Set<Integer> fieldSet;
        private long objectId;
@@ -211,6 +212,7 @@ public class NXCObjectModificationData
        private String sshPassword;
        private long zoneProxy;
        private List<ObjectUrl> urls;
+   private List<Long> seedObjectIds;
        
        /**
         * Constructor for creating modification data for given object
@@ -1757,4 +1759,21 @@ public class NXCObjectModificationData
       this.urls = urls;
       fieldSet.add(URL_LIST);
    }
+   
+   /**
+    * @return the seedObjectIds
+    */
+   public Long[] getSeedObjectIds()
+   {
+      return seedObjectIds.toArray(new Long[seedObjectIds.size()]);
+   }
+   
+   /**
+    * @param seedObjectIds the seed node object Ids to set
+    */
+   public void setSeedObjectIds(List<Long> seedObjectIds)
+   {
+      this.seedObjectIds = seedObjectIds;
+      fieldSet.add(SEED_OBJECTS);
+   }
 }
index 3c9a819..49f3db3 100644 (file)
@@ -4351,7 +4351,7 @@ public class NXCSession
             break;
          case AbstractObject.OBJECT_NETWORKMAP:
             msg.setFieldInt16(NXCPCodes.VID_MAP_TYPE, data.getMapType());
-            msg.setFieldInt32(NXCPCodes.VID_SEED_OBJECT, (int) data.getSeedObjectId());
+            msg.setField(NXCPCodes.VID_SEED_OBJECTS, data.getSeedObjectIds());
             msg.setFieldInt32(NXCPCodes.VID_FLAGS, (int) data.getFlags());
             break;
          case AbstractObject.OBJECT_NETWORKSERVICE:
@@ -4935,6 +4935,11 @@ public class NXCSession
       {
          msg.setFieldInt32(NXCPCodes.VID_ZONE_PROXY, (int)data.getZoneProxy());
       }
+      
+      if (data.isFieldSet(NXCObjectModificationData.SEED_OBJECTS))
+      {
+         msg.setField(NXCPCodes.VID_SEED_OBJECTS, data.getSeedObjectIds());
+      }
             
       modifyCustomObject(data, userData, msg);
 
index f2d6c81..a2a3c92 100644 (file)
@@ -19,6 +19,7 @@
 package org.netxms.client.objects;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 import org.netxms.base.GeoLocation;
@@ -58,7 +59,7 @@ public class NetworkMap extends GenericObject
        private UUID background;
        private GeoLocation backgroundLocation;
        private int backgroundZoom;
-       private long seedObjectId;
+       private Long seedObjectIds[];
        private int defaultLinkColor;
        private int defaultLinkRouting;
        private MapObjectDisplayMode objectDisplayMode;
@@ -81,7 +82,7 @@ public class NetworkMap extends GenericObject
                background = msg.getFieldAsUUID(NXCPCodes.VID_BACKGROUND);
                backgroundLocation = new GeoLocation(msg.getFieldAsDouble(NXCPCodes.VID_BACKGROUND_LATITUDE), msg.getFieldAsDouble(NXCPCodes.VID_BACKGROUND_LONGITUDE));
                backgroundZoom = msg.getFieldAsInt32(NXCPCodes.VID_BACKGROUND_ZOOM);
-               seedObjectId = msg.getFieldAsInt64(NXCPCodes.VID_SEED_OBJECT);
+               seedObjectIds = msg.getFieldAsUInt32ArrayEx(NXCPCodes.VID_SEED_OBJECTS);
                defaultLinkColor = msg.getFieldAsInt32(NXCPCodes.VID_LINK_COLOR);
                defaultLinkRouting = msg.getFieldAsInt32(NXCPCodes.VID_LINK_ROUTING);
                objectDisplayMode = MapObjectDisplayMode.getByValue(msg.getFieldAsInt32(NXCPCodes.VID_DISPLAY_MODE));
@@ -117,7 +118,7 @@ public class NetworkMap extends GenericObject
        public void prepareCopy(NXCObjectCreationData cd, NXCObjectModificationData md)
        {
           cd.setMapType(mapType);
-          cd.setSeedObjectId(seedObjectId);
+          cd.setSeedObjectIds(Arrays.asList(seedObjectIds));
           
           md.setMapLayout(layout);
           md.setMapBackground(background, backgroundLocation, backgroundZoom, backgroundColor);
@@ -169,11 +170,11 @@ public class NetworkMap extends GenericObject
        }
 
        /**
-        * @return the seedObjectId
+        * @return the seedObjectIds
         */
-       public long getSeedObjectId()
+       public Long[] getSeedObjectIds()
        {
-               return seedObjectId;
+               return seedObjectIds;
        }
        
        /**
index e01914b..c767b42 100644 (file)
@@ -31,6 +31,7 @@ import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Text;
 import org.netxms.client.objects.Node;
 import org.netxms.ui.eclipse.networkmaps.Messages;
+import org.netxms.ui.eclipse.objectbrowser.dialogs.ObjectSelectionDialog;
 import org.netxms.ui.eclipse.objectbrowser.widgets.ObjectSelector;
 import org.netxms.ui.eclipse.tools.MessageDialogHelper;
 import org.netxms.ui.eclipse.tools.WidgetHelper;
@@ -108,6 +109,7 @@ public class CreateNetworkMapDialog extends Dialog
       seedObjectSelector = new ObjectSelector(dialogArea, SWT.NONE, true);
       seedObjectSelector.setLabel(Messages.get().CreateNetworkMapDialog_SeedNode);
       seedObjectSelector.setObjectClass(Node.class);
+      seedObjectSelector.setClassFilter(ObjectSelectionDialog.createNodeSelectionFilter(false));
       seedObjectSelector.setEnabled(false);
       gd = new GridData();
       gd.horizontalAlignment = SWT.FILL;
index a913715..92c1380 100644 (file)
               </instanceof>
            </enabledWhen>
         </page>
+        <page
+              class="org.netxms.ui.eclipse.objectmanager.propertypages.MapSeedNodes"
+              id="org.netxms.ui.eclipse.objectmanager.MapSeedNodes"
+              name="Map Seed Nodes">
+           <enabledWhen>
+              <instanceof
+                    value="org.netxms.client.objects.NetworkMap">
+              </instanceof>
+           </enabledWhen>
+        </page>
   </extension>
 
    <extension
diff --git a/src/java/netxms-eclipse/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/propertypages/MapSeedNodes.java b/src/java/netxms-eclipse/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/propertypages/MapSeedNodes.java
new file mode 100644 (file)
index 0000000..1e540e4
--- /dev/null
@@ -0,0 +1,260 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2017 Raden Solutions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.objectmanager.propertypages;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.netxms.client.NXCObjectModificationData;
+import org.netxms.client.NXCSession;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.client.objects.NetworkMap;
+import org.netxms.ui.eclipse.jobs.ConsoleJob;
+import org.netxms.ui.eclipse.objectbrowser.dialogs.ObjectSelectionDialog;
+import org.netxms.ui.eclipse.objectmanager.Activator;
+import org.netxms.ui.eclipse.shared.ConsoleSharedData;
+import org.netxms.ui.eclipse.tools.WidgetHelper;
+
+/**
+ * Map seed nodes property page
+ */
+public class MapSeedNodes extends PropertyPage
+{
+   public static final int COLUMN_NAME = 0;
+   private AbstractObject object = null;
+   private TableViewer viewer;
+   private Button addButton;
+   private Button deleteButton;
+   private Set<AbstractObject> seedNodes;
+   private boolean isModified = false;
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
+    */
+   @Override
+   protected Control createContents(Composite parent)
+   {
+      Composite dialogArea = new Composite(parent, SWT.NONE);
+      object = (AbstractObject)getElement().getAdapter(AbstractObject.class);
+      if (object == null || !(object instanceof NetworkMap))
+         return dialogArea;
+      
+      GridLayout layout = new GridLayout();
+      layout.verticalSpacing = WidgetHelper.OUTER_SPACING;
+      layout.marginWidth = 0;
+      layout.marginHeight = 0;
+      dialogArea.setLayout(layout);
+      
+      viewer = new TableViewer(dialogArea, SWT.BORDER);
+      viewer.setContentProvider(new ArrayContentProvider());
+      viewer.setLabelProvider(new LabelProvider() {
+         /* (non-Javadoc)
+          * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
+          */
+         @Override
+         public String getText(Object element)
+         {
+             return ((AbstractObject)element).getObjectName();
+         }
+         
+      });
+      
+      NXCSession session = (NXCSession)ConsoleSharedData.getSession();
+      seedNodes = new HashSet<AbstractObject>(session.findMultipleObjects(((NetworkMap)object).getSeedObjectIds(), false));
+      viewer.setInput(seedNodes);
+      
+      GridData gridData = new GridData();
+      gridData.verticalAlignment = GridData.FILL;
+      gridData.grabExcessVerticalSpace = true;
+      gridData.horizontalAlignment = GridData.FILL;
+      gridData.grabExcessHorizontalSpace = true;
+      gridData.heightHint = 0;
+      viewer.getControl().setLayoutData(gridData);
+      
+      Composite buttons = new Composite(dialogArea, SWT.NONE);
+      RowLayout buttonLayout = new RowLayout();
+      buttonLayout.type = SWT.HORIZONTAL;
+      buttonLayout.pack = false;
+      buttonLayout.marginWidth = 0;
+      buttonLayout.marginRight = 0;
+      buttons.setLayout(buttonLayout);
+      gridData = new GridData();
+      gridData.horizontalAlignment = SWT.RIGHT;
+      buttons.setLayoutData(gridData);
+      
+      addButton = new Button(buttons, SWT.PUSH);
+      addButton.setText("Add...");
+      RowData rd = new RowData();
+      rd.width = WidgetHelper.BUTTON_WIDTH_HINT;
+      addButton.setLayoutData(rd);
+      addButton.addSelectionListener(new SelectionListener() {
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetSelected(SelectionEvent e)
+         {
+            final ObjectSelectionDialog dlg = new ObjectSelectionDialog(getShell(), null, ObjectSelectionDialog.createNodeSelectionFilter(false));
+            dlg.enableMultiSelection(true);
+            if (dlg.open() == Window.OK)
+            {
+               List<AbstractObject> selected = dlg.getSelectedObjects();
+               if (selected.size() > 0)
+               {
+                  seedNodes.addAll(selected);
+                  viewer.setInput(seedNodes);
+                  isModified = true;
+               }                  
+            }
+         }
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetDefaultSelected(SelectionEvent e)
+         {
+            widgetSelected(e);
+         }
+      });
+      
+      deleteButton = new Button(buttons, SWT.PUSH);
+      deleteButton.setText("Delete");
+      rd = new RowData();
+      rd.width = WidgetHelper.BUTTON_WIDTH_HINT;
+      deleteButton.setLayoutData(rd);
+      deleteButton.addSelectionListener(new SelectionListener() {
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetSelected(SelectionEvent e)
+         {
+            IStructuredSelection selection = (IStructuredSelection)viewer.getSelection();
+            if (selection.size() > 0)
+            {
+               seedNodes.removeAll(selection.toList());
+               viewer.setInput(seedNodes);
+               isModified = true;
+            }
+         }
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetDefaultSelected(SelectionEvent e)
+         {
+            widgetSelected(e);
+         }
+      });      
+      
+      return dialogArea;
+   }
+   
+   /**
+    * Apply changes
+    * @param isApply true if update operation caused by "Apply" button
+    */
+   protected void applyChanges(final boolean isApply)
+   {
+      if (!isModified)
+         return;     // Nothing to apply
+   
+      if (isApply)
+         setValid(false);
+   
+      final NXCObjectModificationData md = new NXCObjectModificationData(object.getObjectId());
+      List<Long> seedObjectIds = new ArrayList<Long>();
+      for(AbstractObject o : seedNodes)
+         seedObjectIds.add(o.getObjectId());      
+      md.setSeedObjectIds(seedObjectIds);
+      
+      final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
+      new ConsoleJob("Update network map seed nodes", null, Activator.PLUGIN_ID, null) {
+         @Override
+         protected void runInternal(IProgressMonitor monitor) throws Exception
+         {
+            session.modifyObject(md);
+            isModified = false;
+         }
+   
+         @Override
+         protected String getErrorMessage()
+         {
+            return "Network map seed node update failed";
+         }
+   
+         @Override
+         protected void jobFinalize()
+         {
+            if (isApply)
+            {
+               runInUIThread(new Runnable() {
+                  @Override
+                  public void run()
+                  {
+                     MapSeedNodes.this.setValid(true);
+                  }
+               });
+            }
+         }
+      }.start();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.preference.PreferencePage#performApply()
+    */
+   @Override
+   protected void performApply()
+   {
+      applyChanges(true);
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.preference.PreferencePage#performOk()
+    */
+   @Override
+   public boolean performOk()
+   {
+      applyChanges(false);
+      return true;
+   }
+
+}
\ No newline at end of file
index 66bc64e..06b28fc 100644 (file)
@@ -61,10 +61,10 @@ NetworkMapLink::NetworkMapLink(NXCPMessage *msg, UINT32 baseId)
  */
 NetworkMapLink::~NetworkMapLink()
 {
-       safe_free(m_name);
-       safe_free(m_connectorName1);
-       safe_free(m_connectorName2);
-       safe_free(m_config);
+       free(m_name);
+       free(m_connectorName1);
+       free(m_connectorName2);
+       free(m_config);
 }
 
 
@@ -106,6 +106,20 @@ void NetworkMapLink::setConnector2Name(const TCHAR *name)
 }
 
 /**
+ * Swap connector information
+ */
+void NetworkMapLink::swap()
+{
+   UINT32 te = m_element1;
+   m_element1 = m_element2;
+   m_element2 = te;
+
+   TCHAR *tn = m_connectorName1;
+   m_connectorName1 = m_connectorName2;
+   m_connectorName2 = tn;
+}
+
+/**
  * Fill NXCP message
  */
 void NetworkMapLink::fillMessage(NXCPMessage *msg, UINT32 baseId)
index 70619e2..e5ea5ab 100644 (file)
@@ -44,7 +44,6 @@ bool NetworkMapGroup::showThresholdSummary()
 NetworkMap::NetworkMap() : NetObj()
 {
        m_mapType = NETMAP_USER_DEFINED;
-       m_seedObject = 0;
        m_discoveryRadius = -1;
        m_flags = MF_SHOW_STATUS_ICON;
        m_layout = MAP_LAYOUT_MANUAL;
@@ -61,15 +60,16 @@ NetworkMap::NetworkMap() : NetObj()
        m_links = new ObjectArray<NetworkMapLink>(0, 32, true);
    m_filterSource = NULL;
    m_filter = NULL;
+   m_seedObjects = new IntegerArray<UINT32>();
 }
 
 /**
  * Create network map object from user session
  */
-NetworkMap::NetworkMap(int type, UINT32 seed) : NetObj()
+NetworkMap::NetworkMap(int type, IntegerArray<UINT32> *seeds) : NetObj()
 {
        m_mapType = type;
-       m_seedObject = seed;
+       m_seedObjects = new IntegerArray<UINT32>(seeds);
        m_discoveryRadius = -1;
        m_flags = MF_SHOW_STATUS_ICON;
    m_layout = (type == NETMAP_USER_DEFINED) ? MAP_LAYOUT_MANUAL : MAP_LAYOUT_SPRING;
@@ -97,6 +97,7 @@ NetworkMap::~NetworkMap()
        delete m_elements;
        delete m_links;
    delete m_filter;
+   delete m_seedObjects;
    safe_free(m_filterSource);
 }
 
@@ -244,30 +245,29 @@ BOOL NetworkMap::saveToDatabase(DB_HANDLE hdb)
        DB_STATEMENT hStmt;
        if (IsDatabaseRecordExist(hdb, _T("network_maps"), _T("id"), m_id))
        {
-               hStmt = DBPrepare(hdb, _T("UPDATE network_maps SET map_type=?,layout=?,seed=?,radius=?,background=?,bg_latitude=?,bg_longitude=?,bg_zoom=?,flags=?,link_color=?,link_routing=?,bg_color=?,object_display_mode=?,filter=? WHERE id=?"));
+               hStmt = DBPrepare(hdb, _T("UPDATE network_maps SET map_type=?,layout=?,radius=?,background=?,bg_latitude=?,bg_longitude=?,bg_zoom=?,flags=?,link_color=?,link_routing=?,bg_color=?,object_display_mode=?,filter=? WHERE id=?"));
        }
        else
        {
-               hStmt = DBPrepare(hdb, _T("INSERT INTO network_maps (map_type,layout,seed,radius,background,bg_latitude,bg_longitude,bg_zoom,flags,link_color,link_routing,bg_color,object_display_mode,filter,id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"));
+               hStmt = DBPrepare(hdb, _T("INSERT INTO network_maps (map_type,layout,radius,background,bg_latitude,bg_longitude,bg_zoom,flags,link_color,link_routing,bg_color,object_display_mode,filter,id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"));
        }
        if (hStmt == NULL)
                goto fail;
 
        DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, (INT32)m_mapType);
        DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, (INT32)m_layout);
-       DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, m_seedObject);
-       DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, (INT32)m_discoveryRadius);
-       DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, m_background);
-       DBBind(hStmt, 6, DB_SQLTYPE_DOUBLE, m_backgroundLatitude);
-       DBBind(hStmt, 7, DB_SQLTYPE_DOUBLE, m_backgroundLongitude);
-       DBBind(hStmt, 8, DB_SQLTYPE_INTEGER, (INT32)m_backgroundZoom);
-       DBBind(hStmt, 9, DB_SQLTYPE_INTEGER, m_flags);
-       DBBind(hStmt, 10, DB_SQLTYPE_INTEGER, (INT32)m_defaultLinkColor);
-       DBBind(hStmt, 11, DB_SQLTYPE_INTEGER, (INT32)m_defaultLinkRouting);
-       DBBind(hStmt, 12, DB_SQLTYPE_INTEGER, (INT32)m_backgroundColor);
-       DBBind(hStmt, 13, DB_SQLTYPE_INTEGER, (INT32)m_objectDisplayMode);
-       DBBind(hStmt, 14, DB_SQLTYPE_VARCHAR, m_filterSource, DB_BIND_STATIC);
-       DBBind(hStmt, 15, DB_SQLTYPE_INTEGER, m_id);
+       DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, (INT32)m_discoveryRadius);
+       DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, m_background);
+       DBBind(hStmt, 5, DB_SQLTYPE_DOUBLE, m_backgroundLatitude);
+       DBBind(hStmt, 6, DB_SQLTYPE_DOUBLE, m_backgroundLongitude);
+       DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, (INT32)m_backgroundZoom);
+       DBBind(hStmt, 8, DB_SQLTYPE_INTEGER, m_flags);
+       DBBind(hStmt, 9, DB_SQLTYPE_INTEGER, (INT32)m_defaultLinkColor);
+       DBBind(hStmt, 10, DB_SQLTYPE_INTEGER, (INT32)m_defaultLinkRouting);
+       DBBind(hStmt, 11, DB_SQLTYPE_INTEGER, (INT32)m_backgroundColor);
+       DBBind(hStmt, 12, DB_SQLTYPE_INTEGER, (INT32)m_objectDisplayMode);
+       DBBind(hStmt, 13, DB_SQLTYPE_VARCHAR, m_filterSource, DB_BIND_STATIC);
+       DBBind(hStmt, 14, DB_SQLTYPE_INTEGER, m_id);
 
        if (!DBExecute(hStmt))
        {
@@ -322,6 +322,26 @@ BOOL NetworkMap::saveToDatabase(DB_HANDLE hdb)
    }
        DBFreeStatement(hStmt);
 
+       // Save seed nodes
+   if (executeQueryOnObject(hdb, _T("DELETE FROM network_map_seed_nodes WHERE map_id=?")))
+   {
+      hStmt = DBPrepare(hdb, _T("INSERT INTO network_map_seed_nodes (map_id,seed_node_id) VALUES (?,?)"));
+      if (hStmt != NULL)
+      {
+         for(int i = 0; i < m_seedObjects->size(); i++)
+         {
+            DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
+            DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_seedObjects->get(i));
+            if (!DBExecute(hStmt))
+            {
+               DBFreeStatement(hStmt);
+               goto fail;
+            }
+         }
+         DBFreeStatement(hStmt);
+      }
+   }
+
        unlockProperties();
        return TRUE;
 
@@ -342,6 +362,8 @@ bool NetworkMap::deleteFromDatabase(DB_HANDLE hdb)
       success = executeQueryOnObject(hdb, _T("DELETE FROM network_map_elements WHERE map_id=?"));
    if (success)
       success = executeQueryOnObject(hdb, _T("DELETE FROM network_map_links WHERE map_id=?"));
+   if (success)
+      success = executeQueryOnObject(hdb, _T("DELETE FROM network_map_seed_nodes WHERE map_id=?"));
    return success;
 }
 
@@ -364,26 +386,25 @@ bool NetworkMap::loadFromDatabase(DB_HANDLE hdb, UINT32 dwId)
 
           loadACLFromDB(hdb);
 
-               _sntprintf(query, 256, _T("SELECT map_type,layout,seed,radius,background,bg_latitude,bg_longitude,bg_zoom,flags,link_color,link_routing,bg_color,object_display_mode,filter FROM network_maps WHERE id=%d"), dwId);
+               _sntprintf(query, 256, _T("SELECT map_type,layout,radius,background,bg_latitude,bg_longitude,bg_zoom,flags,link_color,link_routing,bg_color,object_display_mode,filter FROM network_maps WHERE id=%d"), dwId);
                DB_RESULT hResult = DBSelect(hdb, query);
                if (hResult == NULL)
                        return false;
 
                m_mapType = DBGetFieldLong(hResult, 0, 0);
                m_layout = DBGetFieldLong(hResult, 0, 1);
-               m_seedObject = DBGetFieldULong(hResult, 0, 2);
-               m_discoveryRadius = DBGetFieldLong(hResult, 0, 3);
-               m_background = DBGetFieldGUID(hResult, 0, 4);
-               m_backgroundLatitude = DBGetFieldDouble(hResult, 0, 5);
-               m_backgroundLongitude = DBGetFieldDouble(hResult, 0, 6);
-               m_backgroundZoom = (int)DBGetFieldLong(hResult, 0, 7);
-               m_flags = DBGetFieldULong(hResult, 0, 8);
-               m_defaultLinkColor = DBGetFieldLong(hResult, 0, 9);
-               m_defaultLinkRouting = DBGetFieldLong(hResult, 0, 10);
-               m_backgroundColor = DBGetFieldLong(hResult, 0, 11);
-               m_objectDisplayMode = DBGetFieldLong(hResult, 0, 12);
-
-      TCHAR *filter = DBGetField(hResult, 0, 13, NULL, 0);
+               m_discoveryRadius = DBGetFieldLong(hResult, 0, 2);
+               m_background = DBGetFieldGUID(hResult, 0, 3);
+               m_backgroundLatitude = DBGetFieldDouble(hResult, 0, 4);
+               m_backgroundLongitude = DBGetFieldDouble(hResult, 0, 5);
+               m_backgroundZoom = (int)DBGetFieldLong(hResult, 0, 6);
+               m_flags = DBGetFieldULong(hResult, 0, 7);
+               m_defaultLinkColor = DBGetFieldLong(hResult, 0, 8);
+               m_defaultLinkRouting = DBGetFieldLong(hResult, 0, 9);
+               m_backgroundColor = DBGetFieldLong(hResult, 0, 10);
+               m_objectDisplayMode = DBGetFieldLong(hResult, 0, 11);
+
+      TCHAR *filter = DBGetField(hResult, 0, 12, NULL, 0);
       setFilter(filter);
       safe_free(filter);
 
@@ -463,6 +484,24 @@ bool NetworkMap::loadFromDatabase(DB_HANDLE hdb, UINT32 dwId)
                        }
          DBFreeResult(hResult);
       }
+
+      // Load seed nodes
+      DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT seed_node_id FROM network_map_seed_nodes WHERE map_id=?"));
+      if (hStmt != NULL)
+      {
+         DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
+         hResult = DBSelectPrepared(hStmt);
+         if (hResult != NULL)
+         {
+            int nRows = DBGetNumRows(hResult);
+            for(int i = 0; i < nRows; i++)
+            {
+               m_seedObjects->add(DBGetFieldULong(hResult, i, 0));
+            }
+            DBFreeResult(hResult);
+         }
+      }
+      DBFreeStatement(hStmt);
        }
 
        m_status = STATUS_NORMAL;
@@ -480,7 +519,7 @@ void NetworkMap::fillMessageInternal(NXCPMessage *msg)
        msg->setField(VID_MAP_TYPE, (WORD)m_mapType);
        msg->setField(VID_LAYOUT, (WORD)m_layout);
        msg->setField(VID_FLAGS, m_flags);
-       msg->setField(VID_SEED_OBJECT, m_seedObject);
+       msg->setFieldFromInt32Array(VID_SEED_OBJECTS, m_seedObjects);
        msg->setField(VID_DISCOVERY_RADIUS, (UINT32)m_discoveryRadius);
        msg->setField(VID_BACKGROUND, m_background);
        msg->setField(VID_BACKGROUND_LATITUDE, m_backgroundLatitude);
@@ -527,8 +566,8 @@ UINT32 NetworkMap::modifyFromMessageInternal(NXCPMessage *request)
                m_flags |= (request->getFieldAsUInt32(VID_FLAGS) & mask);
        }
 
-       if (request->isFieldExist(VID_SEED_OBJECT))
-               m_seedObject = request->getFieldAsUInt32(VID_SEED_OBJECT);
+       if (request->isFieldExist(VID_SEED_OBJECTS))
+               request->getFieldAsInt32Array(VID_SEED_OBJECTS, m_seedObjects);
 
        if (request->isFieldExist(VID_DISCOVERY_RADIUS))
                m_discoveryRadius = (int)request->getFieldAsUInt32(VID_DISCOVERY_RADIUS);
@@ -621,178 +660,209 @@ UINT32 NetworkMap::modifyFromMessageInternal(NXCPMessage *request)
  */
 void NetworkMap::updateContent()
 {
-       DbgPrintf(6, _T("NetworkMap::updateContent(%s [%d]): map type %d, seed %d"), m_name, m_id, m_mapType, m_seedObject);
-       Node *seed;
-       switch(m_mapType)
-       {
-               case MAP_TYPE_LAYER2_TOPOLOGY:
-                       seed = (Node *)FindObjectById(m_seedObject, OBJECT_NODE);
-                       if (seed != NULL)
-                       {
-                               UINT32 status;
-                               nxmap_ObjList *objects = seed->buildL2Topology(&status, m_discoveryRadius, (m_flags & MF_SHOW_END_NODES) != 0);
-                               if (objects != NULL)
-                               {
-                                       updateObjects(objects);
-                                       delete objects;
-                               }
-                               else
-                               {
-                                       DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): call to buildL2Topology on object %d failed"), m_name, m_id, m_seedObject);
-                               }
-                       }
-                       else
-                       {
-                               DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): seed object %d cannot be found"), m_name, m_id, m_seedObject);
-                       }
-                       break;
-               case MAP_TYPE_IP_TOPOLOGY:
-                       seed = (Node *)FindObjectById(m_seedObject, OBJECT_NODE);
-                       if (seed != NULL)
-                       {
-                               UINT32 status;
-                               nxmap_ObjList *objects = seed->buildIPTopology(&status, m_discoveryRadius, (m_flags & MF_SHOW_END_NODES) != 0);
-                               if (objects != NULL)
-                               {
-                                       updateObjects(objects);
-                                       delete objects;
-                               }
-                               else
-                               {
-                                       DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): call to BuildIPTopology on object %d failed"), m_name, m_id, m_seedObject);
-                               }
-                       }
-                       else
-                       {
-                               DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): seed object %d cannot be found"), m_name, m_id, m_seedObject);
-                       }
-                       break;
-               default:
-                       break;
-       }
-       DbgPrintf(6, _T("NetworkMap::updateContent(%s [%d]): completed"), m_name, m_id);
+   DbgPrintf(6, _T("NetworkMap::updateContent(%s [%d]): map type %d"), m_name, m_id, m_mapType);
+   nxmap_ObjList *objectList;
+   ObjectArray<nxmap_ObjList> objects;
+   UINT32 status;
+   Node *seed;
+   for(int i = 0; i < m_seedObjects->size(); i++)
+   {
+      seed = (Node *)FindObjectById(m_seedObjects->get(i), OBJECT_NODE);
+      if (seed != NULL)
+      {
+         switch(m_mapType)
+         {
+            case MAP_TYPE_LAYER2_TOPOLOGY:
+            {
+               objectList = seed->buildL2Topology(&status, m_discoveryRadius, (m_flags & MF_SHOW_END_NODES) != 0);
+               if (objectList != NULL)
+                  objects.add(objectList);
+               else
+                  DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): call to buildL2Topology on object %d failed"), m_name, m_id, m_seedObjects->get(i));;
+               break;
+            }
+            case MAP_TYPE_IP_TOPOLOGY:
+            {
+               objectList = seed->buildIPTopology(&status, m_discoveryRadius, (m_flags & MF_SHOW_END_NODES) != 0);
+               if (objectList != NULL)
+                  objects.add(objectList);
+               else
+                  DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): call to BuildIPTopology on object %d failed"), m_name, m_id, m_seedObjects->get(i));
+               break;
+            }
+            default:
+               break;
+         }
+      }
+      else
+         DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): seed object %d cannot be found"), m_name, m_id, m_seedObjects->get(i));
+   }
+   if (objects.size() > 0)
+   {
+      updateObjects(&objects);
+      DbgPrintf(6, _T("NetworkMap::updateContent(%s [%d]): completed"), m_name, m_id);
+   }
+   else
+      DbgPrintf(3, _T("NetworkMap::updateContent(%s [%d]): failed"), m_name, m_id);
 }
 
 /**
  * Update objects from given list
  */
-void NetworkMap::updateObjects(nxmap_ObjList *objects)
+void NetworkMap::updateObjects(ObjectArray<nxmap_ObjList> *objects)
 {
-       bool modified = false;
+   bool modified = false;
 
-       DbgPrintf(5, _T("NetworkMap(%s): updateObjects called"), m_name);
+   DbgPrintf(5, _T("NetworkMap(%s): updateObjects called"), m_name);
 
-   // Filter out unallowed objects
-   if ((m_flags & MF_FILTER_OBJECTS) && (m_filter != NULL) && (objects->getNumObjects() > 0))
+   // Filter out disallowed objects
+   if ((m_flags & MF_FILTER_OBJECTS) && (m_filter != NULL))
    {
-      IntegerArray<UINT32> *idList = objects->getObjects();
-      for(int i = 0; i < idList->size(); i++)
+      for(int j = 0; j < objects->size(); j++)
       {
-         NetObj *object = FindObjectById(idList->get(i));
-         if ((object == NULL) || !isAllowedOnMap(object))
+         if (objects->get(j)->getNumObjects() > 0)
          {
-            idList->remove(i);
-            i--;
+            IntegerArray<UINT32> *idList = objects->get(j)->getObjects();
+            for(int i = 0; i < idList->size(); i++)
+            {
+               NetObj *object = FindObjectById(idList->get(i));
+               if ((object == NULL) || !isAllowedOnMap(object))
+               {
+                  idList->remove(i);
+                  i--;
+               }
+            }
          }
       }
    }
 
-       lockProperties();
-
-       // remove non-existing links
-       for(int i = 0; i < m_links->size(); i++)
-       {
-               NetworkMapLink *link = m_links->get(i);
-               UINT32 objID1 = objectIdFromElementId(link->getElement1());
-               UINT32 onjID2 = objectIdFromElementId(link->getElement2());
-               if (!objects->isLinkExist(objID1, onjID2) && link->checkFlagSet(AUTO_GENERATED))
-               {
-                       DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: link %d - %d removed"), m_name, link->getElement1(), link->getElement2());
-                       m_links->remove(i);
-                       i--;
-                       modified = true;
-               }
-       }
-
-       // remove non-existing objects
-       for(int i = 0; i < m_elements->size(); i++)
-       {
-               NetworkMapElement *e = m_elements->get(i);
-               if (e->getType() != MAP_ELEMENT_OBJECT)
-                       continue;
+   lockProperties();
 
-               if (!objects->isObjectExist(((NetworkMapObject *)e)->getObjectId()) && (e->getFlags() & AUTO_GENERATED) )
-               {
-                       DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: object element %d removed"), m_name, e->getId());
-                       m_elements->remove(i);
-                       i--;
-                       modified = true;
-               }
-       }
+   // remove non-existing links
+   for(int i = 0; i < m_links->size(); i++)
+   {
+      NetworkMapLink *link = m_links->get(i);
+      if (!link->checkFlagSet(AUTO_GENERATED))
+         continue;
+      UINT32 objID1 = objectIdFromElementId(link->getElement1());
+      UINT32 objID2 = objectIdFromElementId(link->getElement2());
+      bool linkExists = false;
+      for(int j = 0; j < objects->size(); j++)
+      {
+         if (objects->get(j)->isLinkExist(objID1, objID2))
+         {
+            linkExists = true;
+            break;
+         }
+         if (objects->get(j)->isLinkExist(objID2, objID1))
+         {
+            link->swap();
+            linkExists = true;
+            break;
+         }
+      }
+      if (!linkExists)
+      {
+         DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: link %d - %d removed"), m_name, link->getElement1(), link->getElement2());
+         m_links->remove(i);
+         i--;
+         modified = true;
+      }
+   }
 
-       // add new objects
-       for(int i = 0; i < objects->getNumObjects(); i++)
-       {
-               bool found = false;
-               for(int j = 0; j < m_elements->size(); j++)
-               {
-                       NetworkMapElement *e = m_elements->get(j);
-                       if (e->getType() != MAP_ELEMENT_OBJECT)
-                               continue;
-                       if (((NetworkMapObject *)e)->getObjectId() == objects->getObjects()->get(i))
-                       {
-                               found = true;
-                               break;
-                       }
-               }
-               if (!found)
-               {
-                       NetworkMapElement *e = new NetworkMapObject(m_nextElementId++, objects->getObjects()->get(i), 1);
-                       m_elements->add(e);
-                       modified = true;
-                       DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: new object %d added"), m_name, objects->getObjects()->get(i));
-               }
-       }
+   // remove non-existing objects
+   for(int i = 0; i < m_elements->size(); i++)
+   {
+      NetworkMapElement *e = m_elements->get(i);
+      if ((e->getType() != MAP_ELEMENT_OBJECT) || !(e->getFlags() & AUTO_GENERATED))
+         continue;
+      bool objectExists = false;
+      for(int j = 0; j < objects->size(); j++)
+      {
+         if (objects->get(j)->isObjectExist(((NetworkMapObject *)e)->getObjectId()))
+         {
+            objectExists = true;
+            break;
+         }
+      }
+      if (!objectExists)
+      {
+         DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: object element %d removed"), m_name, e->getId());
+         m_elements->remove(i);
+         i--;
+         modified = true;
+      }
+   }
 
-       // add new links
-       for(int i = 0; i < objects->getNumLinks(); i++)
-       {
-               bool found = false;
-               for(int j = 0; j < m_links->size(); j++)
-               {
-                       NetworkMapLink *l = m_links->get(j);
-                       UINT32 obj1 = objectIdFromElementId(l->getElement1());
-                       UINT32 obj2 = objectIdFromElementId(l->getElement2());
-                       if ((objects->getLinks()->get(i)->id1 == obj1) && (objects->getLinks()->get(i)->id2 == obj2))
-                       {
-                               found = true;
-                               break;
-                       }
-               }
-               if (!found)
-               {
-                       UINT32 e1 = elementIdFromObjectId(objects->getLinks()->get(i)->id1);
-                       UINT32 e2 = elementIdFromObjectId(objects->getLinks()->get(i)->id2);
-                       // Element ID can be 0 if link points to object removed by filter
-                       if ((e1 != 0) && (e2 != 0))
-                       {
-            NetworkMapLink *l = new NetworkMapLink(e1, e2, objects->getLinks()->get(i)->type);
-            l->setConnector1Name(objects->getLinks()->get(i)->port1);
-            l->setConnector2Name(objects->getLinks()->get(i)->port2);
-            l->setConfig(objects->getLinks()->get(i)->config);
-            l->setFlags(1);
-            m_links->add(l);
+   // add new objects
+   for(int k = 0; k < objects->size(); k++)
+   {
+      for(int i = 0; i < objects->get(k)->getNumObjects(); i++)
+      {
+         bool found = false;
+         for(int j = 0; j < m_elements->size(); j++)
+         {
+            NetworkMapElement *e = m_elements->get(j);
+            if (e->getType() != MAP_ELEMENT_OBJECT)
+               continue;
+            if (((NetworkMapObject *)e)->getObjectId() == objects->get(k)->getObjects()->get(i))
+            {
+               found = true;
+               break;
+            }
+         }
+         if (!found)
+         {
+            NetworkMapElement *e = new NetworkMapObject(m_nextElementId++, objects->get(k)->getObjects()->get(i), 1);
+            m_elements->add(e);
             modified = true;
-            DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: link %d - %d added"), m_name, l->getElement1(), l->getElement2());
-                       }
-               }
-       }
+            DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: new object %d added"), m_name, objects->get(k)->getObjects()->get(i));
+         }
+      }
+   }
+
+   // add new links
+   for(int k = 0; k < objects->size(); k++)
+   {
+      for(int i = 0; i < objects->get(k)->getNumLinks(); i++)
+      {
+         bool found = false;
+         for(int j = 0; j < m_links->size(); j++)
+         {
+            NetworkMapLink *l = m_links->get(j);
+            UINT32 obj1 = objectIdFromElementId(l->getElement1());
+            UINT32 obj2 = objectIdFromElementId(l->getElement2());
+            if ((objects->get(k)->getLinks()->get(i)->id1 == obj1) && (objects->get(k)->getLinks()->get(i)->id2 == obj2))
+            {
+               found = true;
+               break;
+            }
+         }
+         if (!found)
+         {
+            UINT32 e1 = elementIdFromObjectId(objects->get(k)->getLinks()->get(i)->id1);
+            UINT32 e2 = elementIdFromObjectId(objects->get(k)->getLinks()->get(i)->id2);
+            // Element ID can be 0 if link points to object removed by filter
+            if ((e1 != 0) && (e2 != 0))
+            {
+               NetworkMapLink *l = new NetworkMapLink(e1, e2, objects->get(k)->getLinks()->get(i)->type);
+               l->setConnector1Name(objects->get(k)->getLinks()->get(i)->port1);
+               l->setConnector2Name(objects->get(k)->getLinks()->get(i)->port2);
+               l->setConfig(objects->get(k)->getLinks()->get(i)->config);
+               l->setFlags(AUTO_GENERATED);
+               m_links->add(l);
+               modified = true;
+               DbgPrintf(5, _T("NetworkMap(%s)/updateObjects: link %d - %d added"), m_name, l->getElement1(), l->getElement2());
+            }
+         }
+      }
+   }
 
-       if (modified)
-               setModified();
+   if (modified)
+      setModified();
 
-       unlockProperties();
-       DbgPrintf(5, _T("NetworkMap(%s): updateObjects completed"), m_name);
+   unlockProperties();
+   DbgPrintf(5, _T("NetworkMap(%s): updateObjects completed"), m_name);
 }
 
 /**
@@ -900,7 +970,6 @@ bool NetworkMap::isAllowedOnMap(NetObj *object)
 void NetworkMap::onObjectDelete(UINT32 dwObjectId)
 {
    lockProperties();
-
    UINT32 elementId = elementIdFromObjectId(dwObjectId);
    int i = 0;
    while(i < m_links->size())
index fcb7520..bbd106a 100644 (file)
@@ -5126,9 +5126,13 @@ void ClientSession::createObject(NXCPMessage *request)
                            NetObjInsert(object, true, false);
                            break;
                         case OBJECT_NETWORKMAP:
-                           object = new NetworkMap((int)request->getFieldAsUInt16(VID_MAP_TYPE), request->getFieldAsUInt32(VID_SEED_OBJECT));
-                           object->setName(objectName);
-                           NetObjInsert(object, true, false);
+                           {
+                              IntegerArray<UINT32> seeds;
+                              request->getFieldAsInt32Array(VID_SEED_OBJECTS, &seeds);
+                              object = new NetworkMap((int)request->getFieldAsUInt16(VID_MAP_TYPE), &seeds);
+                              object->setName(objectName);
+                              NetObjInsert(object, true, false);
+                           }
                            break;
                         case OBJECT_NETWORKMAPGROUP:
                            object = new NetworkMapGroup(objectName);
index 8d26d65..552d56a 100644 (file)
@@ -2347,7 +2347,7 @@ class NXCORE_EXPORTABLE NetworkMap : public NetObj
 {
 protected:
        int m_mapType;
-       UINT32 m_seedObject;
+       IntegerArray<UINT32> *m_seedObjects;
        int m_discoveryRadius;
        int m_layout;
        UINT32 m_flags;
@@ -2368,7 +2368,7 @@ protected:
    virtual void fillMessageInternal(NXCPMessage *pMsg);
    virtual UINT32 modifyFromMessageInternal(NXCPMessage *pRequest);
 
-       void updateObjects(nxmap_ObjList *objects);
+       void updateObjects(ObjectArray<nxmap_ObjList> *lists);
        UINT32 objectIdFromElementId(UINT32 eid);
        UINT32 elementIdFromObjectId(UINT32 eid);
 
@@ -2376,7 +2376,7 @@ protected:
 
 public:
    NetworkMap();
-       NetworkMap(int type, UINT32 seed);
+       NetworkMap(int type, IntegerArray<UINT32> *seeds);
    virtual ~NetworkMap();
 
    virtual int getObjectClass() const { return OBJECT_NETWORKMAP; }
index ece53e9..9961235 100644 (file)
@@ -747,6 +747,51 @@ static bool SetSchemaVersion(int version)
 }
 
 /**
+ * Upgrade from V445 to V446
+ */
+static BOOL H_UpgradeFromV445(int currVersion, int newVersion)
+{
+   CHK_EXEC(CreateTable(
+      _T("CREATE TABLE network_map_seed_nodes (")
+      _T("  map_id integer not null,")
+      _T("  seed_node_id integer not null,")
+      _T("PRIMARY KEY(map_id,seed_node_id))")));
+
+   DB_RESULT hResult = DBSelect(g_hCoreDB, _T("SELECT id,seed FROM network_maps"));
+   DB_STATEMENT hStmt = DBPrepare(g_hCoreDB, _T("INSERT INTO network_map_seed_nodes (map_id,seed_node_id) VALUES (?,?)"));
+   if (hResult != NULL)
+   {
+      if (hStmt != NULL)
+      {
+         int nRows = DBGetNumRows(hResult);
+         for(int i = 0; i < nRows; i++)
+         {
+            DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, DBGetFieldULong(hResult, i, 0));
+            DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, DBGetFieldULong(hResult, i, 1));
+
+            if (!SQLExecute(hStmt))
+            {
+               if (!g_bIgnoreErrors)
+               {
+                  DBFreeStatement(hStmt);
+                  DBFreeResult(hResult);
+                  return FALSE;
+               }
+            }
+         }
+
+         CHK_EXEC(SQLDropColumn(_T("network_maps"), _T("seed")));
+         DBFreeStatement(hStmt);
+      }
+      DBFreeResult(hResult);
+   }
+
+
+   CHK_EXEC(SetSchemaVersion(446));
+   return TRUE;
+}
+
+/**
  * Upgrade from V444 to V445
  */
 static BOOL H_UpgradeFromV444(int currVersion, int newVersion)
@@ -11614,6 +11659,7 @@ static struct
    { 442, 443, H_UpgradeFromV442 },
    { 443, 444, H_UpgradeFromV443 },
    { 444, 445, H_UpgradeFromV444 },
+   { 445, 446, H_UpgradeFromV445 },
    { 0, 0, NULL }
 };
 
index e01914b..c767b42 100644 (file)
@@ -31,6 +31,7 @@ import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Text;
 import org.netxms.client.objects.Node;
 import org.netxms.ui.eclipse.networkmaps.Messages;
+import org.netxms.ui.eclipse.objectbrowser.dialogs.ObjectSelectionDialog;
 import org.netxms.ui.eclipse.objectbrowser.widgets.ObjectSelector;
 import org.netxms.ui.eclipse.tools.MessageDialogHelper;
 import org.netxms.ui.eclipse.tools.WidgetHelper;
@@ -108,6 +109,7 @@ public class CreateNetworkMapDialog extends Dialog
       seedObjectSelector = new ObjectSelector(dialogArea, SWT.NONE, true);
       seedObjectSelector.setLabel(Messages.get().CreateNetworkMapDialog_SeedNode);
       seedObjectSelector.setObjectClass(Node.class);
+      seedObjectSelector.setClassFilter(ObjectSelectionDialog.createNodeSelectionFilter(false));
       seedObjectSelector.setEnabled(false);
       gd = new GridData();
       gd.horizontalAlignment = SWT.FILL;
index a913715..af95958 100644 (file)
               </instanceof>
            </enabledWhen>
         </page>
+        <page
+              class="org.netxms.ui.eclipse.objectmanager.propertypages.MapSeedNodes"
+              id="org.netxms.ui.eclipse.objectmanager.propertypages.MapSeedNodes"
+              name="Map Seed Nodes">
+           <enabledWhen>
+              <instanceof
+                    value="org.netxms.client.objects.NetworkMap">
+              </instanceof>
+           </enabledWhen>
+        </page>
   </extension>
 
    <extension
diff --git a/webui/webapp/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/propertypages/MapSeedNodes.java b/webui/webapp/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/propertypages/MapSeedNodes.java
new file mode 100644 (file)
index 0000000..0d207d3
--- /dev/null
@@ -0,0 +1,260 @@
+/**
+ * NetXMS - open source network management system
+ * Copyright (C) 2003-2017 Raden Solutions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.netxms.ui.eclipse.objectmanager.propertypages;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.netxms.client.NXCObjectModificationData;
+import org.netxms.client.NXCSession;
+import org.netxms.client.objects.AbstractObject;
+import org.netxms.client.objects.NetworkMap;
+import org.netxms.ui.eclipse.jobs.ConsoleJob;
+import org.netxms.ui.eclipse.objectbrowser.dialogs.ObjectSelectionDialog;
+import org.netxms.ui.eclipse.objectmanager.Activator;
+import org.netxms.ui.eclipse.shared.ConsoleSharedData;
+import org.netxms.ui.eclipse.tools.WidgetHelper;
+
+/**
+ * Map seed nodes property page
+ */
+public class MapSeedNodes extends PropertyPage
+{
+   public static final int COLUMN_NAME = 0;
+   private AbstractObject object = null;
+   private TableViewer viewer;
+   private Button addButton;
+   private Button deleteButton;
+   private Set<AbstractObject> seedNodes;
+   private boolean isModified = false;
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
+    */
+   @Override
+   protected Control createContents(Composite parent)
+   {
+      Composite dialogArea = new Composite(parent, SWT.NONE);
+      object = (AbstractObject)getElement().getAdapter(AbstractObject.class);
+      if (object == null || !(object instanceof NetworkMap))
+         return dialogArea;
+      
+      GridLayout layout = new GridLayout();
+      layout.verticalSpacing = WidgetHelper.OUTER_SPACING;
+      layout.marginWidth = 0;
+      layout.marginHeight = 0;
+      dialogArea.setLayout(layout);
+      
+      viewer = new TableViewer(dialogArea, SWT.BORDER);
+      viewer.setContentProvider(new ArrayContentProvider());
+      viewer.setLabelProvider(new LabelProvider() {
+         /* (non-Javadoc)
+          * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
+          */
+         @Override
+         public String getText(Object element)
+         {
+             return ((AbstractObject)element).getObjectName();
+         }
+         
+      });
+      
+      NXCSession session = (NXCSession)ConsoleSharedData.getSession();
+      seedNodes = new HashSet<AbstractObject>(session.findMultipleObjects(((NetworkMap)object).getSeedObjectIds(), false));
+      viewer.setInput(seedNodes);
+      
+      GridData gridData = new GridData();
+      gridData.verticalAlignment = GridData.FILL;
+      gridData.grabExcessVerticalSpace = true;
+      gridData.horizontalAlignment = GridData.FILL;
+      gridData.grabExcessHorizontalSpace = true;
+      gridData.heightHint = 0;
+      viewer.getControl().setLayoutData(gridData);
+      
+      Composite buttons = new Composite(dialogArea, SWT.NONE);
+      RowLayout buttonLayout = new RowLayout();
+      buttonLayout.type = SWT.HORIZONTAL;
+      buttonLayout.pack = false;
+      buttonLayout.marginWidth = 0;
+      buttonLayout.marginRight = 0;
+      buttons.setLayout(buttonLayout);
+      gridData = new GridData();
+      gridData.horizontalAlignment = SWT.RIGHT;
+      buttons.setLayoutData(gridData);
+      
+      addButton = new Button(buttons, SWT.PUSH);
+      addButton.setText("Add...");
+      RowData rd = new RowData();
+      rd.width = WidgetHelper.BUTTON_WIDTH_HINT;
+      addButton.setLayoutData(rd);
+      addButton.addSelectionListener(new SelectionListener() {
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetSelected(SelectionEvent e)
+         {
+            final ObjectSelectionDialog dlg = new ObjectSelectionDialog(getShell(), null, ObjectSelectionDialog.createNodeSelectionFilter(false));
+            dlg.enableMultiSelection(true);
+            if (dlg.open() == Window.OK)
+            {
+               List<AbstractObject> selected = dlg.getSelectedObjects();
+               if (selected.size() > 0)
+               {
+                  seedNodes.addAll(selected);
+                  viewer.setInput(seedNodes);
+                  isModified = true;
+               }                  
+            }
+         }
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetDefaultSelected(SelectionEvent e)
+         {
+            widgetSelected(e);
+         }
+      });
+      
+      deleteButton = new Button(buttons, SWT.PUSH);
+      deleteButton.setText("Delete");
+      rd = new RowData();
+      rd.width = WidgetHelper.BUTTON_WIDTH_HINT;
+      deleteButton.setLayoutData(rd);
+      deleteButton.addSelectionListener(new SelectionListener() {
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetSelected(SelectionEvent e)
+         {
+            IStructuredSelection selection = (IStructuredSelection)viewer.getSelection();
+            if (selection.size() > 0)
+            {
+               seedNodes.removeAll(selection.toList());
+               viewer.setInput(seedNodes);
+               isModified = true;
+            }
+         }
+         
+         /* (non-Javadoc)
+          * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
+          */
+         @Override
+         public void widgetDefaultSelected(SelectionEvent e)
+         {
+            widgetSelected(e);
+         }
+      });      
+      
+      return dialogArea;
+   }
+   
+   /**
+    * Apply changes
+    * @param isApply true if update operation caused by "Apply" button
+    */
+   protected void applyChanges(final boolean isApply)
+   {
+      if (!isModified)
+         return;     // Nothing to apply
+   
+      if (isApply)
+         setValid(false);
+   
+      final NXCObjectModificationData md = new NXCObjectModificationData(object.getObjectId());
+      List<Long> seedObjectIds = new ArrayList<Long>();
+      for(AbstractObject o : seedNodes)
+         seedObjectIds.add(o.getObjectId());      
+      md.setSeedObjectIds(seedObjectIds);
+      
+      final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
+      new ConsoleJob("Update network map seed nodes", null, Activator.PLUGIN_ID, null) {
+         @Override
+         protected void runInternal(IProgressMonitor monitor) throws Exception
+         {
+            session.modifyObject(md);
+            isModified = false;
+         }
+   
+         @Override
+         protected String getErrorMessage()
+         {
+            return "Network map seed node update failed";
+         }
+   
+         @Override
+         protected void jobFinalize()
+         {
+            if (isApply)
+            {
+               runInUIThread(new Runnable() {
+                  @Override
+                  public void run()
+                  {
+                     MapSeedNodes.this.setValid(true);
+                  }
+               });
+            }
+         }
+      }.start();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.preference.PreferencePage#performApply()
+    */
+   @Override
+   protected void performApply()
+   {
+      applyChanges(true);
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.preference.PreferencePage#performOk()
+    */
+   @Override
+   public boolean performOk()
+   {
+      applyChanges(false);
+      return true;
+   }
+
+}