Most Automation Orchestrator plug-ins require an endpoint configuration. An endpoint is a location where the plug-in stores connection details for the instances, with which Automation Orchestrator communicates.

Procedure

  1. Under the {plug-in-home}/o11nplugin-redis-core/src/main/java/com/vmware/o11n/plugin/redis folder, create a directory with the name model.
    This directory constitutes the com.vmware.o11n.plugin.redis.model package of the plug-in.
  2. Create an endpoint configuration.
    The following example shows a plain old Java object ( POJO) that is used to store connection details for the plug-in endpoint, such as connection name, host name, and host port.
    package com.vmware.o11n.plugin.redis.model; 
    import org.springframework.util.Assert; 
    import com.vmware.o11n.sdk.modeldriven.Sid; 
    /** 
     * Object for all the redis connection details. 
     */ 
    public class ConnectionInfo { 
        /** 
         * Name of the connection as defined by the user when creating a connection 
         */ 
        private String name; 
        /** 
         * ID of the connection 
         */ 
        private final Sid id; 
        /** 
         * Service URI of the third party system 
         */ 
        private String host; 
        /** 
         * Port of the connection, defaults the to redis default port number 
         */ 
        private int port = 6379; 
        public ConnectionInfo() { 
            this.id = Sid.unique(); 
        } 
        /* 
         * Verify that each ConnectionInfo has an ID. 
         */ 
        public ConnectionInfo(Sid id) { 
            super(); 
            Assert.notNull(id, "Id cannot be null."); 
            this.id = id; 
        } 
        public String getName() { 
            return name; 
        } 
        public void setName(String name) { 
            this.name = name; 
        } 
        public String getHost() { 
            return host; 
        } 
        public void setHost(String host) { 
            this.host = host; 
        } 
        public Sid getId() { 
            return id; 
        } 
        public void setPort(int port) { 
            this.port = port; 
        } 
        public int getPort() { 
            return port; 
        } 
        @Override 
        public String toString() { 
            return "ConnectionInfo [name=" + name + ", id=" + id + ", uri=" + host + ", port=" + port + "]"; 
        } 
    } 
  3. Specify an ID, so that the default constructor can find a unique ID.
    Plug-ins use Sid instead of a string ID. When you build hierarchies of object, each child object must know its parent ID. A Sid wraps multiple key-value objects and you can identify a single object by different properties.
    For example, a single configuration can have multiple connections, each connection has a list of locations and each location has a list of virtual machine images. When you run certain operation, for example delete, on a virtual machine image, you must know the specific connection, to which you run the command. By using a Sid, you can identify the virtual machine image object by connection ID, name, and location.
  4. Save the endpoint configuration as a ConnectionInfo.java file in the model directory.
  5. Wrap ConnectionInfo to Connection to expose the actual object as a scripting object
    package com.vmware.o11n.plugin.redis.model; 
    import org.springframework.beans.factory.annotation.Qualifier; 
    import org.springframework.context.annotation.Scope; 
    import org.springframework.stereotype.Component; 
    @Component 
    @Qualifier(value = "connection") 
    @Scope(value = "prototype") 
    public class Connection { 
        /* 
         * The connectionInfo which stands behind this live connection. 
         */ 
        private ConnectionInfo connectionInfo; 
        /* 
         * There is no default constructor, the Connection must be initialized only 
         * with a connection info argument. 
         */ 
        public Connection(ConnectionInfo info) { 
            init(info); 
        } 
        public synchronized ConnectionInfo getConnectionInfo() { 
            return connectionInfo; 
        } 
        public String getDisplayName() { 
            return getConnectionInfo().getName() + " [" + getConnectionInfo().getHost() + "]"; 
        } 
        /* 
         * Updates this connection with the provided info. This operation will 
         * destroy the existing third party client, causing all associated 
         * operations to fail. 
         */ 
        public synchronized void update(ConnectionInfo connectionInfo) { 
            if (this.connectionInfo != null && !connectionInfo.getId().equals(this.connectionInfo.getId())) { 
                throw new IllegalArgumentException("Cannot update using different id"); 
            } 
            destroy(); 
            init(connectionInfo); 
        } 
        private void init(ConnectionInfo connectionInfo) { 
            this.connectionInfo = connectionInfo; 
        } 
        public synchronized void destroy() { 
            // Destroy the third party client 
        } 
    } 
    Note: After the Connection object is exposed as a scripting object, it's also exposed as an inventory object.
  6. Save the Connection object as a Connection.java file in the model directory.
  7. Under the {plug-in-home}/o11nplugin-redis-core/src/main/java/com/vmware/o11n/plugin/redis folder, create a directory with name config.
    This directory constitutes the com.vmware.o11n.plugin.redis.config package of the plug-in.
  8. Add a connection, persister, and interface for object messaging to the com.vmware.o11n.plugin.redis.config package.
    1. Add ConnectionPersister and save it as a ConnectionPersister.java file.
      Note: ConnectionPersister invokes the Automation Orchestrator Persistence SDK.
      package com.vmware.o11n.plugin.redis.config; 
      import java.util.List; 
      import com.vmware.o11n.plugin.redis.model.ConnectionInfo; 
      import com.vmware.o11n.sdk.modeldriven.Sid; 
      public interface ConnectionPersister { 
          /** 
           * Returns a collection of all stored configurations (resources under a 
           * folder with the plug-in name) 
           */ 
          public List<ConnectionInfo> findAll(); 
          /** 
           * Returns a collection by its ID or null if not found 
           */ 
          public ConnectionInfo findById(Sid id); 
          /** 
           * Stores a connection info or updates it if already available. The 
           * persister checks the availability of a connection by its ID 
           */ 
          public ConnectionInfo save(ConnectionInfo connection); 
          /** 
           * Deletes a connection info. The persister will use the ID of the 
           * connection 
           */ 
          public void delete(ConnectionInfo connectionInfo); 
          /** 
           * Allows us to subscribe to the events of the persister. For example, if a 
           * connection is deleted, the persister will trigger an event, notifying all 
           * subscribers. This is an implementation of the observer pattern. 
           */ 
          void addChangeListener(ConfigurationChangeListener listener); 
          /** 
           * Forces the persister to read all the configurations and trigger the 
           * events. This method is invoked when the plug-in is loaded on server 
           * start-up. 
           */ 
          public void load(); 
      } 
    2. Add DefaultConnectionPersister and save it as a DefaultConnectionPersister.java file.
      package com.vmware.o11n.plugin.redis.config; 
      import java.io.IOException; 
      import java.util.ArrayList; 
      import java.util.Collection; 
      import java.util.List; 
      import java.util.concurrent.CopyOnWriteArrayList; 
      import org.apache.commons.lang.Validate; 
      import org.slf4j.Logger; 
      import org.slf4j.LoggerFactory; 
      import org.springframework.beans.factory.annotation.Autowired; 
      import org.springframework.stereotype.Component; 
      import com.vmware.o11n.plugin.redis.model.ConnectionInfo; 
      import com.vmware.o11n.sdk.modeldriven.Sid; 
      import ch.dunes.vso.sdk.endpoints.IEndpointConfiguration; 
      import ch.dunes.vso.sdk.endpoints.IEndpointConfigurationService; 
      @Component 
      public class DefaultConnectionPersister implements ConnectionPersister { 
          private static final Logger log = LoggerFactory.getLogger(DefaultConnectionPersister.class); 
          /** 
           * A list of listeners, who have subscribed to any configuration events, 
           * such as connection updates and deletions. 
           */ 
          private final Collection<ConfigurationChangeListener> listeners; 
          public DefaultConnectionPersister() { 
              // Initialise the listeners 
              listeners = new CopyOnWriteArrayList<ConfigurationChangeListener>(); 
          } 
          /* 
           * Constants of the key names under which the connection values will be 
           * stored. 
           */ 
          private static final String ID = "connectionId"; 
          private static final String NAME = "name"; 
          private static final String SERVICE_HOST = "serviceHost"; 
          private static final String SERVICE_PORT = "servicePort"; 
          /* 
           * The platform-provided service for configuration persistence 
           */ 
          @Autowired 
          private IEndpointConfigurationService endpointConfigurationService; 
          /* 
           * Returns a collection of all stored configurations for this plug-in only 
           * The service is aware of the plug-in name, thus will return only 
           * configurations for this plug-in. 
           */ 
          @Override 
          public List<ConnectionInfo> findAll() { 
              Collection<IEndpointConfiguration> configs; 
              try { 
                  // Use the configuration service to retrieve all configurations. 
                  // The service is aware of the plug-in name, thus will return only 
                  // configurations for this plug-in. 
                  configs = endpointConfigurationService.getEndpointConfigurations(); 
                  List<ConnectionInfo> result = new ArrayList<>(configs.size()); 
                  // Iterate all the connections 
                  for (IEndpointConfiguration config : configs) { 
                      // Convert the IEndpointConfiguration to our domain object - the 
                      // ConnectionInfo 
                      ConnectionInfo connectionInfo = getConnectionInfo(config); 
                      if (connectionInfo != null) { 
                          log.debug("Adding connection info to result map: " + connectionInfo); 
                          result.add(connectionInfo); 
                      } 
                  } 
                  return result; 
              } catch (IOException e) { 
                  log.debug("Error reading connections.", e); 
                  throw new RuntimeException(e); 
              } 
          } 
          /* 
           * Returns a ConnectionInfo by its ID The service is aware of the plug-in 
           * name, thus cannot return a configuration for another plug-in. 
           */ 
          @Override 
          public ConnectionInfo findById(Sid id) { 
              // Sanity checks 
              Validate.notNull(id, "Sid cannot be null."); 
              IEndpointConfiguration endpointConfiguration; 
              try { 
                  // Use the configuration service to retrieve the configuration 
                  // service by its ID 
                  endpointConfiguration = endpointConfigurationService.getEndpointConfiguration(id.toString()); 
                  // Convert the IEndpointConfiguration to our domain object - the 
                  // ConnectionInfo 
                  return getConnectionInfo(endpointConfiguration); 
              } catch (IOException e) { 
                  log.debug("Error finding connection by id: " + id.toString(), e); 
                  throw new RuntimeException(e); 
              } 
          } 
          /** 
           * Save or update a connection info. The service is aware of the plug-in 
           * name, thus cannot save the configuration under the name of another 
           * plug-in. 
           */ 
          @Override 
          public ConnectionInfo save(ConnectionInfo connectionInfo) { 
              // Sanity checks 
              Validate.notNull(connectionInfo, "Connection info cannot be null."); 
              Validate.notNull(connectionInfo.getId(), "Connection info must have an id."); 
              // Additional validation - in this case we want the name of the 
              // connection to be unique 
              validateConnectionName(connectionInfo); 
              try { 
                  // Find a connection with the provided ID. We don't expect to have 
                  // an empty ID 
                  IEndpointConfiguration endpointConfiguration = endpointConfigurationService 
                          .getEndpointConfiguration(connectionInfo.getId().toString()); 
                  // If the configuration is null, then we are performing a save 
                  // operation 
                  if (endpointConfiguration == null) { 
                      // Use the configuration service to create a new (empty) 
                      // IEndpointConfiguration. 
                      // In this case, we are responsible for assigning the ID of the 
                      // configuration, 
                      // which is done in the constructor of the ConnectionInfo 
                      endpointConfiguration = endpointConfigurationService 
                              .newEndpointConfiguration(connectionInfo.getId().toString()); 
                  } 
                  // Convert the ConnectionInfo the IEndpointConfiguration 
                  addConnectionInfoToConfig(endpointConfiguration, connectionInfo); 
                  // Use the configuration service to save the endpoint configuration 
                  endpointConfigurationService.saveEndpointConfiguration(endpointConfiguration); 
                  // Fire an event to all subscribers, that we have updated a 
                  // configuration. 
                  // Pass the entire connectionInfo object and let the subscribers 
                  // decide if they need to do something 
                  fireConnectionUpdated(connectionInfo); 
                  return connectionInfo; 
              } catch (IOException e) { 
                  log.error("Error saving connection " + connectionInfo, e); 
                  throw new RuntimeException(e); 
              } 
          } 
          /** 
           * Delete a connection info. The service is aware of the plug-in name, thus 
           * cannot delete a configuration from another plug-in. 
           */ 
          @Override 
          public void delete(ConnectionInfo connectionInfo) { 
              try { 
                  // Use the configuration service to delete the connection info. The 
                  // service uses the ID 
                  endpointConfigurationService.deleteEndpointConfiguration(connectionInfo.getId().toString()); 
                  // Fire an event to all subscribers, that we have deleted a 
                  // configuration. 
                  // Pass the entire connectionInfo object and let the subscribers 
                  // decide if they need to do something 
                  fireConnectionRemoved(connectionInfo); 
              } catch (IOException e) { 
                  log.error("Error deleting endpoint configuration: " + connectionInfo, e); 
                  throw new RuntimeException(e); 
              } 
          } 
          /** 
           * This method is used to load the entire configuration set of the plug-in. 
           * As a second step we fire a notification to all subscribers. This method 
           * is used when the plug-in is being loaded (on server startup). 
           */ 
          @Override 
          public void load() { 
              List<ConnectionInfo> findAll = findAll(); 
              for (ConnectionInfo connectionInfo : findAll) { 
                  fireConnectionUpdated(connectionInfo); 
              } 
          } 
          /** 
           * Attach a configuration listener which will be called when a connection is 
           * created/updated/deleted 
           */ 
          @Override 
          public void addChangeListener(ConfigurationChangeListener listener) { 
              listeners.add(listener); 
          } 
          /* 
           * A helper method which iterates all event subscribers and fires the update 
           * notification for the provided connection info. 
           */ 
          private void fireConnectionUpdated(ConnectionInfo connectionInfo) { 
              for (ConfigurationChangeListener li : listeners) { 
                  li.connectionUpdated(connectionInfo); 
              } 
          } 
          /* 
           * A helper method which iterates all event subscribers and fires the delete 
           * notification for the provided connection info. 
           */ 
          private void fireConnectionRemoved(ConnectionInfo connectionInfo) { 
              for (ConfigurationChangeListener li : listeners) { 
                  li.connectionRemoved(connectionInfo); 
              } 
          } 
          /* 
           * A helper method which converts our domain object the ConnectionInfo to an 
           * IEndpointConfiguration 
           */ 
          private void addConnectionInfoToConfig(IEndpointConfiguration config, ConnectionInfo info) { 
              try { 
                  config.setString(ID, info.getId().toString()); 
                  config.setString(NAME, info.getName()); 
                  config.setString(SERVICE_HOST, info.getHost()); 
                  config.setInt(SERVICE_PORT, info.getPort()); 
              } catch (Exception e) { 
                  log.error("Error converting ConnectionInfo to IEndpointConfiguration.", e); 
                  throw new RuntimeException(e); 
              } 
          } 
          /* 
           * A helper method which converts the IEndpointConfiguration to our domain 
           * object the ConnectionInfo 
           */ 
          private ConnectionInfo getConnectionInfo(IEndpointConfiguration config) { 
              ConnectionInfo info = null; 
              try { 
                  Sid id = Sid.valueOf(config.getString(ID)); 
                  info = new ConnectionInfo(id); 
                  info.setName(config.getString(NAME)); 
                  info.setHost(config.getString(SERVICE_HOST)); 
                  info.setPort(config.getAsInteger(SERVICE_PORT)); 
              } catch (IllegalArgumentException e) { 
                  log.warn("Cannot convert IEndpointConfiguration to ConnectionInfo: " + config.getId(), e); 
              } 
              return info; 
          } 
          /* 
           * A helper method which validates if a connection with the same name 
           * already exists. 
           */ 
          private void validateConnectionName(ConnectionInfo connectionInfo) { 
              ConnectionInfo configurationByName = getConfigurationByName(connectionInfo.getName()); 
              if (configurationByName != null 
                      && !configurationByName.getId().toString().equals(connectionInfo.getId().toString())) { 
                  throw new RuntimeException("Connection with the same name already exists: " + connectionInfo); 
              } 
          } 
          /* 
           * A helper method which gets a connection by name. 
      */ 
      private ConnectionInfo getConfigurationByName(String name) { 
      Validate.notNull(name, "Connection name cannot be null."); 
      Collection<ConnectionInfo> findAllClientInfos = findAll(); 
      for (ConnectionInfo info : findAllClientInfos) { 
      if (name.equals(info.getName())) { 
      return info; 
      } 
      } 
      return null; 
      } 
      }
      The DefaultConnectionPersister class includes the IEndpointConfigurationService service that is specific for Automation Orchestrator. IEndpointConfigurationService stores in the resources of the Automation Orchestrator platform simple key-value objects called IEndpointConfiguration objects. This service saves and deletes the configuration, and also validates connection configurations, if these configurations have a unique name system-wide.
    3. Add a collection of ConfigurationChangeListener interfaces.
    4. Save the interface as a ConfigurationChangeListener.java file in the configuration directory.
      You can use the ConfigurationChangeListener interfaces as an extension point of the IEndpointConfigurationService service.
      package com.vmware.o11n.plugin.redis.config; 
      import com.vmware.o11n.plugin.redis.model.ConnectionInfo; 
      /** 
       * An extension point of the configuration persister. Serves the role of an 
       * Observer when a certain connection is created, modified or deleted. 
       */ 
      public interface ConfigurationChangeListener { 
          /** 
           * Invoked when a connection is updated or created 
           */ 
          void connectionUpdated(ConnectionInfo info); 
          /** 
           * Invoked when the ConnectionInfo input is deleted 
           */ 
          void connectionRemoved(ConnectionInfo info); 
      } 

      This way, you implement the Observer pattern from Creating the Configuration Change Listener. If you want to cache all live connections, you must know which of the connections are deleted, so that you can remove them from the local cache.

    5. Create a local cache by using ConnectionRepository saved as a ConnectionRepository.java file in the configuration directory.
      package com.vmware.o11n.plugin.redis.config; 
      import java.util.Collection; 
      import java.util.Map; 
      import java.util.concurrent.ConcurrentHashMap; 
      import org.springframework.beans.BeansException; 
      import org.springframework.beans.factory.InitializingBean; 
      import org.springframework.beans.factory.annotation.Autowired; 
      import org.springframework.context.ApplicationContext; 
      import org.springframework.context.ApplicationContextAware; 
      import org.springframework.stereotype.Component; 
      import com.vmware.o11n.plugin.redis.model.Connection; 
      import com.vmware.o11n.plugin.redis.model.ConnectionInfo; 
      import com.vmware.o11n.sdk.modeldriven.Sid; 
      @Component 
      public class ConnectionRepository implements ApplicationContextAware, InitializingBean, ConfigurationChangeListener { 
          /* 
           * Injecting the ConnectionPersister 
           */ 
          @Autowired 
          private ConnectionPersister persister; 
          private ApplicationContext context; 
          /* 
           * The local map (cache) of live connections 
           */ 
          private final Map<Sid, Connection> connections; 
          public ConnectionRepository() { 
              connections = new ConcurrentHashMap<Sid, Connection>(); 
          } 
          /** 
           * Returns a live connection by its ID or null if no such connection has 
           * been initialised by this ID 
           */ 
          public Connection findLiveConnection(Sid anyId) { 
              return connections.get(anyId.getId()); 
          } 
          /** 
           * Returns all live connections from the local cache 
           */ 
          public Collection<Connection> findAll() { 
              return connections.values(); 
          } 
          /* 
           * Spring-specifics - storing a reference to the spring context 
           */ 
          @Override 
          public void setApplicationContext(ApplicationContext context) throws BeansException { 
              this.context = context; 
          } 
          /* 
           * Spring specifics - this method is being called automatically by the 
           * spring container after all the fields are set and before the bean is 
           * being provided for usage. This method will be called when the plug-in is 
           * being loaded - on server start-up. 
           */ 
          @Override 
          public void afterPropertiesSet() throws Exception { 
              // Subscribing the Repository for any configuration changes that occur 
              // in the Persister 
              persister.addChangeListener(this); 
              // Initialising the Persister. By doing that, the persister will invoke 
              // the connectionUpdated() method 
              // and since we are subscribed to those events, the local cache will be 
              // populated with all the available connections. 
              persister.load(); 
          } 
          private Connection createConnection(ConnectionInfo info) { 
              // This call will create a new spring-managed bean from the context 
              return (Connection) context.getBean("connection", info); 
          } 
          /* 
           * This method will be called from the ConnectionPersister when a new 
           * connection is added or an existing one is updated. 
           */ 
          @Override 
          public void connectionUpdated(ConnectionInfo info) { 
              Connection live = connections.get(info.getId()); 
              if (live != null) { 
                  live.update(info); 
              } else { 
                  // connection just added, create it 
                  live = createConnection(info); 
                  connections.put(info.getId(), live); 
              } 
          } 
          /* 
           * This method will be called from the ConnectionPersister when a connection 
           * is removed. 
           */ 
          @Override 
          public void connectionRemoved(ConnectionInfo info) { 
              Connection live = connections.remove(info.getId()); 
              if (live != null) { 
                  live.destroy(); 
              } 
          } 
      } 
      You can use ConnectionRepository as a local cache to read a single connection or all available connections. A data structure keeps all connections and every time the platform requests a connection, the local cache retrieves the same connection instance.
      Note: You can use ConnectionRepository for read operations and ConnectionPersister for create, update, and delete operations. When a connection is created, the persister notifies all listeners. For example, ConnectionRepository and the local cache stores every new connection.
      For performance reasons, using ConnectionRepository is the preferred method for handling connections. Reading connections with IEndpointConfigurationService might lead to an unnecessary increase in plug-in load.
  9. Use ConnectionManager from the singleton directory to expose the configuration-specific options in the scripting.
    package com.vmware.o11n.plugin.redis.singleton; 
    import org.springframework.beans.factory.annotation.Autowired; 
    import org.springframework.context.annotation.Scope; 
    import org.springframework.stereotype.Component; 
    import com.vmware.o11n.plugin.redis.config.ConnectionPersister; 
    import com.vmware.o11n.plugin.redis.model.ConnectionInfo; 
    import com.vmware.o11n.plugin.sdk.spring.platform.GlobalPluginNotificationHandler; 
    /** 
     * A scripting singleton for managing redis connections. 
     */ 
    @Component 
    @Scope(value = "prototype") 
    public class ConnectionManager { 
        @Autowired 
        private ConnectionPersister persister; 
        /* 
         * A vRO SDK object which is responsible for notifying the platform of 
         * changes in the inventory of the plug-in 
         */ 
        @Autowired 
        private GlobalPluginNotificationHandler notificationHandler; 
        /** 
         * This method creates a redis connection by the provided name, host and 
         * port. 
         * 
         * @param connectionName 
         *            the name of the connection, only one with the same name can 
         *            exist 
         * @param redisHost 
         *            the redis host, cannot be null 
         * @param redisPort 
         *            redis port 
         * @return the ID of the newly created connection 
         */ 
        public String save(String connectionName, String redisHost, int redisPort) { 
            ConnectionInfo info = new ConnectionInfo(); 
            info.setName(connectionName); 
            info.setHost(redisHost); 
            if (redisPort > 0) { 
                info.setPort(redisPort); 
            } 
            // Save the connection through the persister 
            info = persister.save(info); 
            // Invalidate all elements of the redis inventory 
            notificationHandler.notifyElementsInvalidate(); 
            // Return the ID of the newly created connection 
            return info.getId().toString(); 
        } 
    } 
    When you invoke the save method of the singleton scripting object, a new ConnectionInfo class is constructed. You save the connection by using the newly created persister.
    Note: You must annotate the class, otherwise the dependency injection does not work.
  10. Build the plug-in again and deploy it to the Automation Orchestrator server. See Deploy a Plug-In.