You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ri...@apache.org on 2007/04/11 17:21:38 UTC

svn commit: r527518 - in /incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server: management/ registry/ security/access/ security/auth/database/

Author: ritchiem
Date: Wed Apr 11 08:21:37 2007
New Revision: 527518

URL: http://svn.apache.org/viewvc?view=rev&rev=527518
Log:
QPID-446 
JMXManagedObjectRegistry - Split instantiation from starting up. To all the setting of the Access file when loaded later in the startup sequence.
ManagedObjectRegistry - Added Start method
MBeanInvocationHandlerImpl - Updated to allow the setting of the access properties object from the AMQUserManagementMBean
NoopManagedObjectRegistry - implemented no-op start
ConfigurationFileApplicationRegistry - Adjusted to split creation of ManagedObjectRegistry from starting server to allow the setting of access rights.
AMQUserManagementMBean - Implemented reading of access rights file.
Base64MD5PasswordFilePrincipalDatabase - added comment for future Management.
PrincipalDatabaseManager - added initialiseManagement method
ConfigurationFilePrincipalDatabaseManager - implemented general Management initialisation.
PropertiesPrincipalDatabaseManager - no-op implementation



Modified:
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/access/AMQUserManagementMBean.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java
    incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java Wed Apr 11 08:21:37 2007
@@ -76,21 +76,27 @@
 
         // Retrieve the config parameters
         boolean platformServer = appRegistry.getConfiguration().getBoolean("management.platform-mbeanserver", true);
-        boolean security = appRegistry.getConfiguration().getBoolean("management.security-enabled", true);
-        int port = appRegistry.getConfiguration().getInt("management.jmxport", 8999);
 
         _mbeanServer =
                 platformServer ? ManagementFactory.getPlatformMBeanServer()
                 : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN);
+    }
 
+
+    public void start()
+    {
         // Check if the "QPID_OPTS" is set to use Out of the Box JMXAgent
         if (areOutOfTheBoxJMXOptionsSet())
         {
             _log.info("JMX: Using the out of the box JMX Agent");
-
             return;
         }
 
+        IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
+
+        boolean security = appRegistry.getConfiguration().getBoolean("management.security-enabled", true);
+        int port = appRegistry.getConfiguration().getInt("management.jmxport", 8999);
+
         try
         {
             if (security)
@@ -139,7 +145,6 @@
                 try
                 {
                     JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, env, _mbeanServer);
-                    MBeanInvocationHandlerImpl.initialise();
                     MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance();
                     cs.setMBeanServerForwarder(mbsf);
                     cs.start();

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java Wed Apr 11 08:21:37 2007
@@ -44,21 +44,20 @@
 import java.io.FileInputStream;
 
 /**
- * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations.
- * This implements the logic for allowing the users to invoke MBean operations and implements the
- * restrictions for readOnly, readWrite and admin users.
+ * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. This implements
+ * the logic for allowing the users to invoke MBean operations and implements the restrictions for readOnly, readWrite
+ * and admin users.
  */
 public class MBeanInvocationHandlerImpl implements InvocationHandler
 {
     private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class);
-    
+
     private final static String ADMIN = "admin";
-    private final static String READWRITE="readwrite";
+    private final static String READWRITE = "readwrite";
     private final static String READONLY = "readonly";
     private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate";
-    private static final String DEFAULT_PERMISSIONS_FILE = "etc" + File.separator + "jmxremote.access";
     private MBeanServer mbs;
-    private final static Properties _userRoles = new Properties();
+    private static Properties _userRoles = new Properties();
 
     public static MBeanServerForwarder newProxyInstance()
     {
@@ -119,7 +118,7 @@
         {
             throw new SecurityException("Access denied");
         }
-        
+
         Principal principal = principals.iterator().next();
         String identity = principal.getName();
 
@@ -140,21 +139,9 @@
     }
 
     // Initialises the user roles
-    protected static void initialise() throws AMQException
+    public static void setAccessRights(Properties accessRights)
     {
-        final String QpidHome = System.getProperty("QPID_HOME");
-        String fileName = QpidHome + File.separator + DEFAULT_PERMISSIONS_FILE;
-        try
-        {
-            FileInputStream in = new FileInputStream(fileName);
-            _userRoles.load(in);
-            in.close();
-        }
-        catch (IOException ex)
-        {
-            _logger.error("Error in loading JMX User permissions." + ex.getMessage());
-            //throw new AMQException("Error in loading JMX User permissions", ex);
-        }
+        _userRoles = accessRights;
     }
 
     private boolean isAdmin(String userName)
@@ -200,11 +187,13 @@
         {
             String mbeanMethod = (args.length > 1) ? (String) args[1] : null;
             if (mbeanMethod == null)
+            {
                 return false;
-            
+            }
+
             try
             {
-                MBeanInfo mbeanInfo = mbs.getMBeanInfo((ObjectName)args[0]);
+                MBeanInfo mbeanInfo = mbs.getMBeanInfo((ObjectName) args[0]);
                 if (mbeanInfo != null)
                 {
                     MBeanOperationInfo[] opInfos = mbeanInfo.getOperations();
@@ -219,7 +208,7 @@
             }
             catch (JMException ex)
             {
-                ex.printStackTrace();   
+                ex.printStackTrace();
             }
         }
 

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java Wed Apr 11 08:21:37 2007
@@ -37,6 +37,8 @@
  */
 public interface ManagedObjectRegistry
 {
+    void start();
+
     void registerObject(ManagedObject managedObject) throws JMException;
 
     void unregisterObject(ManagedObject managedObject) throws JMException;

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java Wed Apr 11 08:21:37 2007
@@ -40,6 +40,11 @@
         _log.info("Management is disabled");
     }
 
+    public void start()
+    {
+        //no-op
+    }
+
     public void registerObject(ManagedObject managedObject) throws JMException
     {
     }

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java Wed Apr 11 08:21:37 2007
@@ -103,6 +103,8 @@
 
     public void initialise() throws Exception
     {
+        initialiseManagedObjectRegistry();
+
         _virtualHostRegistry = new VirtualHostRegistry();
 
         _accessManager = new AccessManagerImpl("default", _configuration);
@@ -111,8 +113,10 @@
 
         _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null);
 
-        initialiseManagedObjectRegistry();
-        
+        _databaseManager.initialiseManagement(_configuration);
+
+        _managedObjectRegistry.start();
+
         initialiseVirtualHosts();
 
     }

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/access/AMQUserManagementMBean.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/access/AMQUserManagementMBean.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/access/AMQUserManagementMBean.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/access/AMQUserManagementMBean.java Wed Apr 11 08:21:37 2007
@@ -24,9 +24,11 @@
 import org.apache.qpid.server.management.AMQManagedObject;
 import org.apache.qpid.server.management.MBeanOperationParameter;
 import org.apache.qpid.server.management.MBeanOperation;
+import org.apache.qpid.server.management.MBeanInvocationHandlerImpl;
 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
 import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
 import org.apache.log4j.Logger;
+import org.apache.commons.configuration.ConfigurationException;
 
 import javax.management.JMException;
 import javax.management.openmbean.TabularData;
@@ -47,7 +49,7 @@
     private static final Logger _logger = Logger.getLogger(AMQUserManagementMBean.class);
 
     private PrincipalDatabase _principalDatabase;
-    private File _accessFile;
+    private String _accessFile;
 
     Map<String, Principal> _users = new HashMap<String, Principal>();
 
@@ -128,7 +130,15 @@
     {
         try
         {
-            loadAccessFile();
+            try
+            {
+                loadAccessFile();
+            }
+            catch (ConfigurationException e)
+            {
+                _logger.info("Reload failed due to:" + e);
+                return false;
+            }
 
             // Reload successful
             return true;
@@ -144,7 +154,7 @@
     @MBeanOperation(name = "viewUsers", description = "All users with access rights to the system.")
     public TabularData viewUsers()
     {
-        return null;
+        return null;       //todo
     }
 
     /*** Broker Methods **/
@@ -152,9 +162,7 @@
     /**
      * setPrincipalDatabase
      *
-     * @param database
-     *
-     * @throws java.io.IOException If the file cannot be read
+     * @param database set The Database to use for user lookup
      */
     public void setPrincipalDatabase(PrincipalDatabase database)
     {
@@ -166,9 +174,11 @@
      *
      * @param accessFile the file to use for updating.
      *
-     * @throws java.io.IOException If the file cannot be read
+     * @throws java.io.IOException If the file cannot be accessed
+     * @throws org.apache.commons.configuration.ConfigurationException
+     *                             if checks on the file fail.
      */
-    public void setAccessFile(File accessFile) throws IOException
+    public void setAccessFile(String accessFile) throws IOException, ConfigurationException
     {
         _accessFile = accessFile;
 
@@ -182,12 +192,29 @@
         }
     }
 
-    private void loadAccessFile() throws IOException
+    private void loadAccessFile() throws IOException, ConfigurationException
     {
         Properties accessRights = new Properties();
-        accessRights.load(new FileInputStream(_accessFile));
-        processAccessRights(accessRights);
 
+        File access = new File(_accessFile);
+
+        if (!access.exists())
+        {
+            throw new ConfigurationException("'" + _accessFile + "' does not exist");
+        }
+
+        if (!access.canRead())
+        {
+            throw new ConfigurationException("Cannot read '" + _accessFile + "'.");
+        }
+
+        if (!access.canWrite())
+        {
+            _logger.warn("Unable to write to access file '" + _accessFile + "' changes will not be preserved.");
+        }
+
+        accessRights.load(new FileInputStream(access));
+        processAccessRights(accessRights);
     }
 
     /**
@@ -197,6 +224,7 @@
      */
     private void processAccessRights(Properties accessRights)
     {
-        //To change body of created methods use File | Settings | File Templates.
+        _logger.info("Processing Access Rights:" + accessRights);
+        MBeanInvocationHandlerImpl.setAccessRights(accessRights);       
     }
 }

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java Wed Apr 11 08:21:37 2007
@@ -81,15 +81,16 @@
         cram.initialise(this);
         _saslServers.put(cram.getMechanismName(), cram);
 
-        try
-        {
-            _mbean = new AMQUserManagementMBean();
-            _mbean.setPrincipalDatabase(this);
-        }
-        catch (JMException e)
-        {
-            _logger.warn("User management disabled as unable to create MBean:" + e);
-        }
+        //fixme The PDs should setup a PD Mangement MBean
+//        try
+//        {
+//            _mbean = new AMQUserManagementMBean();
+//            _mbean.setPrincipalDatabase(this);
+//        }
+//        catch (JMException e)
+//        {
+//            _logger.warn("User management disabled as unable to create MBean:" + e);
+//        }
     }
 
     public void setPasswordFile(String passwordFile) throws IOException

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java Wed Apr 11 08:21:37 2007
@@ -21,6 +21,7 @@
 package org.apache.qpid.server.security.auth.database;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.List;
@@ -32,9 +33,14 @@
 import org.apache.log4j.Logger;
 
 import org.apache.qpid.configuration.PropertyUtils;
+import org.apache.qpid.configuration.PropertyException;
 import org.apache.qpid.server.registry.ApplicationRegistry;
 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
 import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager;
+import org.apache.qpid.server.security.access.AMQUserManagementMBean;
+import org.apache.qpid.AMQException;
+
+import javax.management.JMException;
 
 public class ConfigurationFilePrincipalDatabaseManager implements PrincipalDatabaseManager
 {
@@ -101,7 +107,7 @@
     }
 
     private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, Configuration config, int index)
-        throws FileNotFoundException, ConfigurationException
+            throws FileNotFoundException, ConfigurationException
     {
         String baseName = _base + "(" + index + ").attributes.attribute.";
         List<String> argumentNames = config.getList(baseName + "name");
@@ -133,9 +139,9 @@
             if (method == null)
             {
                 throw new ConfigurationException("No method " + methodName + " found in class "
-                    + principalDatabase.getClass()
-                    + " hence unable to configure principal database. The method must be public and "
-                    + "have a single String argument with a void return type");
+                                                 + principalDatabase.getClass()
+                                                 + " hence unable to configure principal database. The method must be public and "
+                                                 + "have a single String argument with a void return type");
             }
 
             try
@@ -146,7 +152,7 @@
             {
                 if (ite instanceof ConfigurationException)
                 {
-                    throw (ConfigurationException) ite;
+                    throw(ConfigurationException) ite;
                 }
                 else
                 {
@@ -159,5 +165,72 @@
     public Map<String, PrincipalDatabase> getDatabases()
     {
         return _databases;
+    }
+
+    public void initialiseManagement(Configuration config) throws ConfigurationException
+    {
+        try
+        {
+            AMQUserManagementMBean _mbean = new AMQUserManagementMBean();
+
+            String baseSecurity = "security.jmx";
+            List<String> principalDBs = config.getList(baseSecurity + ".principal-database");
+
+            if (principalDBs.size() == 0)
+            {
+                throw new ConfigurationException("No principal-database specified for jmx security(" + baseSecurity + ".principal-database)");
+            }
+
+            String databaseName = principalDBs.get(0);
+
+            PrincipalDatabase database = getDatabases().get(databaseName);
+
+            if (database == null)
+            {
+                throw new ConfigurationException("Principal-database '" + databaseName + "' not found");
+            }
+
+            _mbean.setPrincipalDatabase(database);
+
+            List<String> jmxaccesslist = config.getList(baseSecurity + ".access");
+
+            if (jmxaccesslist.size() == 0)
+            {
+                throw new ConfigurationException("No access control files specified for jmx security(" + baseSecurity + ".access)");
+            }
+
+            String jmxaccesssFile = null;
+            
+            try
+            {
+                jmxaccesssFile = PropertyUtils.replaceProperties(jmxaccesslist.get(0));
+            }
+            catch (PropertyException e)
+            {
+                throw new ConfigurationException("Unable to parse access control filename '" + jmxaccesssFile + "'");
+            }
+
+            try
+            {
+                _mbean.setAccessFile(jmxaccesssFile);
+            }
+            catch (IOException e)
+            {
+                _logger.warn("Unable to load access file:" + jmxaccesssFile);
+            }
+
+            try
+            {
+                _mbean.register();
+            }
+            catch (AMQException e)
+            {
+                _logger.warn("Unable to register user management MBean");
+            }
+        }
+        catch (JMException e)
+        {
+            _logger.warn("User management disabled as unable to create MBean:" + e);
+        }
     }
 }

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java Wed Apr 11 08:21:37 2007
@@ -21,10 +21,14 @@
 package org.apache.qpid.server.security.auth.database;
 
 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
 
 import java.util.Map;
 
 public interface PrincipalDatabaseManager
 {
     public Map<String, PrincipalDatabase> getDatabases();
+
+    public void initialiseManagement(Configuration config) throws ConfigurationException;
 }

Modified: incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java
URL: http://svn.apache.org/viewvc/incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java?view=diff&rev=527518&r1=527517&r2=527518
==============================================================================
--- incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java (original)
+++ incubator/qpid/branches/M2/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java Wed Apr 11 08:21:37 2007
@@ -20,6 +20,8 @@
  */
 package org.apache.qpid.server.security.auth.database;
 
+import org.apache.commons.configuration.Configuration;
+
 import java.util.Map;
 import java.util.Properties;
 import java.util.HashMap;
@@ -37,5 +39,10 @@
     public Map<String, PrincipalDatabase> getDatabases()
     {
         return _databases;
+    }
+
+    public void initialiseManagement(Configuration config)
+    {
+        //todo
     }
 }