mConsole Programmer's Guide

mConsole provides a rich set of APIs that allow you to customize and extend its capabilities through custom plug-ins. This document describes the internal architecture of mConsole, and the ways to create different types of plug-ins.

Contents:


mConsole Architecture

mConsole consists of two basic layers: management container and plug-ins deployed over the container.

The mConsole container is actually an OSGi framework. Specifically, this is an mBS with core OSGi-compliant bundles providing functionality. You can manage the container as you would do with any mBS. You could add/remove bundles, configure properties, issue console commands, etc.

So, logically, a plug-in for mConsole is actually a normal OSGi-compliant bundle running in the container framework. Plug-ins can share resources by registering and getting OSGi services, and by exporting and importing Java packages. The mConsole container uses the OSGi Framework API for essential plug-in management. The additional API libraries for creating GUI and functional plug-ins are exported in the container framework, so custom plug-ins can use them by importing them in their manifests. The most important components of mConsole's visual interface and functionality are exported as OSGi services so they can be used by custom plug-ins.

The overall structure of mConsole is depicted in Figure 1. It consists of the container framework, the general-purpose plug-ins, and the specific administration plug-ins (mBS-administration plug-ins, mConsole-administration plug-ins, mPRM-administration plug-ins, and your custom ones). Each layer provides services via well-defined API to the components of the above layer.


Figure 1: Overall architecture of mConsole

Let's go through the components of this structure.

Basic mConsole APIs

OSGi Framework API

As already mentioned, the base API used by mConsole plug-ins is the OSGi Framework API (the org.osgi.framework package). It enables plug-ins to share packages and services, and to use the framework's resources.

PGUI API

The GUI components of mConsole are constructed on the basis of the PGUI API library. Menus, toolbars, buttons, consoles, etc. are created using classes defines in PGUI, such as com.prosyst.pgui.PMenu, com.prosyst.pgui.PMenuItem, com.prosyst.pgui.PToolbar, etc. PGUI is based on the standard AWT library, extending its capabilities in a way similar to the Swing library. More information about PGUI is available in the PGUI Tutorial (downloadable from http://dz.prosyst.com/pdoc/).

mConsole API

The visual components and additional functions of mConsole use the mConsole API (the com.prosyst.mc and com.prosyst.mc.login packages) enabling custom plug-ins to plug custom GUI components and administrate different types of servers. The PGUI components, which are the actual building blocks of mConsole, are wrapped by the mConsole API. So, if you need to add GUI components to mConsole, you have to create them through the mConsole API.

Remote Services API

The administration of the services available on the managed OSGi framework is possible through the Remote Services API (all com.prosyst.mc.admin.* packages), exported by the mBS-specific plug-ins. This API provides remote implementation of the most essential OSGi-defined and ProSyst-defined services. The connection to the framework and the remote administration is acquired over the ProSyst Message Protocol (PMP). Detailed description and specification of PMP is available in the Bundles -> RPC Category chapter of this documentation.

Basic OSGi Services in mConsole

Login Services

There is a set of login services running in the mConsole container. Each login service (com.prosyst.mc.login.LoginService) enables mConsole to connect to a specified host and log in the specified system, using its own set of connection and login properties. All are registered under the same service interface but have different registration properties which determine the type of server it will connect (mBS, MC, mPRM, Tracer or a custom type).

Each login service performs two very important functions:

  1. Directs the connection and login to a specified server type.
  2. Defines the GUI components (GUI interfaces) to be loaded when mConsole is connected to that server type.

There are three default login services:

You can also create custom types of login services, that will enable you to use mConsole for login and administration of custom types of servers.

The Login Box of mConsole detects the login services registered in the framework, and uses the connection properties defined for each login service to visualize their settings.

Visual Container Service

The Visual Container (com.prosyst.mc.VisualContainer) service provides access to the basic components of mConsole, such as status bar, error messages, info messages, progress bar, etc. (see Components of the mConsole Window). You can use it to access these basic elements, as well as add visual components (tabs or Settings dialogs) directly to the container.

GUI Factory Services

Each GUI plug-in and property editor containing GUI components, such as menus, toolbars, tabs and subtabs, is represented by a GUI factory (com.prosyst.mc.GUIFactory) service. Each GUI factory is registered with a GUIFactory.GUI_NAME property. It indicates its display name. Optionally, a GUI factory can have a custom display icon, if it is registered with the GUIFactory.GUI_ICON property.

The Visual Container listens for the registration of GUIFactory services. It takes care of displaying the ones that are indicated by the corresponding login service.

Additional GUI Factory Services

Additional GUI factory (com.prosyst.mc.mbs.bundles.AdditionalGUIFactory) services are specific to the MBS server type. If a plug-in needs to add additional GUI components specific to MBS connection type, such as subtabs, bundle tree, etc., it must export an additional GUI factory service. The main idea of additional GUI factories is the same as the idea of GUI factory services. Each additional GUI factory is registered with an AdditionalGUIFactory.GUI_NAME property. It indicates its display name. Optionally, an additional GUI factory can have a custom display icon, if it is registered with the AdditionalGUIFactory.GUI_ICON property.

Types of GUI Components

mConsole GUI components are PGUI elements, but they are not created directly using the PGUI API. The PGUI elements are wrapped in the mConsole API.

As already described, GUIInterface is the parent interface for any GUI component added to mConsole. There are three child interfaces that extend GUIInterface and provide utility methods for the type of component they represent:

In addition, the com.prosyst.mc.SettingsGUIInterface represents components added to the Settings dialog.


Figure 2: Types of GUI components of mConsole

All GUI components must be provided through a GUI Factory service so as to be detected by the Visual Container service.


Creating Basic GUI Components

A basic GUI component of mConsole can be:

Usually, the default GUI plug-ins distributed with mConsole add a uniform set of GUI components: a menu, a toolbar and a tab.

If you want to add other GUI components, such as bundle-related subtabs, or access the bundle tree and existing tabs, refer to the Using the mBS GUI Components section.

To create GUI component(s), take the following steps:

  1. Implement the com.prosyst.mc.GUIInterface interface. It will provide the basic GUI components added to the mConsole container.
  2. Implement the GUIFactory interface and register it as a service. Its getInstance method will return new instances each time or one cached instance of the GUIInterface implementation, created in step 1.

In this section, we shall demonstrate the creation of custom GUI components, using the source code of the GUI Plugin Demo, available at demo/framework/mcplugin.

Implementing GUIInterface

GUIInterface represents the base mConsole GUI component. It provides utility methods which will be used by the visual container to place the component and its toolbar, menu item and setting component (if they exist).

Creating the Plug-in's Main Component

To create the plug-in's main component, you need to implement the getComponent() method of GUIInterface. The main component of a plug-in will be placed in a new tab added to mConsole's main panel (see mConsole General Description: Components of the mConsole Window). The created main component must be returned by the implementation of this method in the form of a com.prosyst.pgui.PComponent object.

Listing 1.1.1 illustrates creating a custom plug-in component. The sample implementation of the getComponent() method constructs a panel from a com.prosyst.pgui.PPanel object.

  public PComponent getComponent() {
    if (mainPanel == null) {
      mainPanel = new PPanel(new GridBagLayout());
      mainPanel.setBorder(new Borders.BevelBorder(Borders.BevelBorder.RAISED));
      PLabel label = new PLabel("This is the Demo Plugin Tab");
      label.setFont(new Font("SansSerif", Font.BOLD, 38));
      mainPanel.add(label, 
            new GridBagConstraints2(0, 0, 1, 1, 1.0, 0.0,
              GridBagConstraints2.CENTER, GridBagConstraints2.NONE, 
                      new Insets(5, 5, 5, 5), 0, 0)
      );
      mainPanel.add(new PLabel(new ImageIcon(DemoToolBar.class.getResource("demo1.gif"))),
        new GridBagConstraints2(0, 1, 1, 1, 1.0, 1.0,
        GridBagConstraints2.CENTER, 
        GridBagConstraints2.NONE, new Insets(0, 5, 5, 5), 0, 0));
    }
    
    return mainPanel;
  }
Listing 1.1.1: Creating the plug-in's main component

Creating a Toolbar

To create a toolbar that will be added to the toolbar area of mConsole, use the getToolBar() method of GUIInterface. The created toolbar must be a com.prosyst.pgui.PToolBar object.

Listing 1.1.2 shows creating the plug-in's toolbar.

  public PToolBar getToolBar() {
    if (demoToolBar == null) {
      demoToolBar = new DemoToolBar(this);
    }
    return demoToolBar;
  }
Listing 1.1.2: Creating the plug-in's toolbar

Creating a Menu

To create a menu for the plug-in, use the getMenuRoot() method of GUIInterface. The created menu must be a com.prosyst.pgui.PMenu object.

Listing 1.1.3 shows creating the plug-in's menu.

  public PMenu getMenuRoot() {
    if (menu == null) {
      menu = new PMenu("Demo Menu");
      
      item = new PMenuItem("Hello");
      item.setIcon(DEMO_GIF);
      item.setActionCommand(GREETING_CMD);
      item.addActionListener(this);
      item.setEnabled(false);
      
      secondItem = new PMenuItem("Progress Demo");
      secondItem.setIcon(DEMO2_GIF);
      secondItem.setActionCommand(THREAD_CMD);
      secondItem.addActionListener(this);
      secondItem.setEnabled(false);
      
      menu.add(item);
      menu.addSeparator();
      menu.add(secondItem);
      
      System.out.println("Demo plug-in: Demo Menu created");
    }
    
    return menu;
  }
Listing 1.1.3: Creating the plug-in's menu

Creating a Settings Dialog

To create a Settings dialog, use the getSettingInterface() method of GUIInterface. The created dialog is a com.prosyst.mc.SettingsGUIInterface object.

Using the Visual Container

Using the Visual Container service allows you to access all mConsole components such as message dialogs, status bar, progress bar, print dialog, etc. The Visual Container service is exported by default in the mConsole container.

To use the Visual Container, you need to get it as a service from the framework. Listing 1.1.4 shows getting the Visual Container service.

visContRef = bc.getServiceReference(VisualContainer.class.getName());
    visContainer = (VisualContainer)bc.getService(visContRef);
Listing 1.1.4: Getting the Visual Container as a service in the mConsole framework

Listing 1.2.2 shows using the progress bar and message dialogs of mConsole through the Visual Container service.

    visContainer.clearProgressBar();
    visContainer.setProgressMaximum(100);
    for (int i = 1; i <= 100; i++) {
      visContainer.updateProgressValue("Current percents: " + i + "%");
      synchronized (this) {
        try {
          this.wait(50);
        } catch (Exception exc) {}
      }
    }
    
    demoGUI.notifyAfterFinished();
    visContainer.clearProgressBar();
    visContainer.showMessageDialog(VisualContainer.INFO_MESSAGE,
      "Hello there!\n The progress demo is finished.",
      "Just a greeting", null);
Listing 1.1.5: Using the Visual Container

Implementing GUIFactory

The GUIFactory interface has a single method, the getInstance(String serverType), which must create and return a new instance of the GUIInterface interface. As already mentioned, this can be a new instance of the GUIInterface each time this method is called, or one cached instance for all method calls.

The parameter passed to this method by mConsole is the connected server type. You can use it to define whether the GUIInterface will be loaded for the current server type. It can be:

Listing 1.2.1 illustrates implementing the getInstance method. We want this sample implementation to add GUI components to mConsole only in MBS connection mode. For that reason, it checks the value of the serverType parameter. If it is not null and it is equal to the constant LoginService.MBS_CONNECTION, then we make a new instance of the GUIInterface implementation and return it:

  public synchronized GUIInterface getInstance(String serverType) {
    if(serverType !=null && serverType.equals(LoginService.MBS_CONNECTION)) {
      gui = new DemoGUI(bc); //our GUIInterface implementation
      return gui;
    } else {
      return null;
    }
  }
Listing 1.2.1: Implementing the getInstance method of GUIFactory

Your bundle must register the com.prosyst.mc.GUIFactory implementation as a service:

  
// register this as a service
Hashtable props = new Hashtable();
props.put(GUI_NAME, "Demo Console Tab");
guiFactoryRegistration = bc.registerService(GUIFactory.class.getName(), 
  this, props);
Listing 1.2.2: Registering the GUI factory service

The name of the tab must be added as a property of the registered service. The name of the property is defined as a constant in the com.prosyst.mc.GUIFactory interface – GUI_NAME.

Note: Optionally, the GUIFactory service can be registered with an additional property – the GUIFactory.GUI_ICON. It will specify the icon with which the GUI component will appear. If you don’t specify an icon, the default ProSyst logo will be used as an icon.


Remotely Calling Services from the OSGi Framework

For connections to MBS server types (OSGi frameworks), mConsole provides remote implementations of the core OSGi-defined services. You can use them instead of trying to get remote reference to the services running on the connected framework over PMP. These are:

In addition, some of the essential ProSyst services have their remote implementations available in the MC API. These are:

All these services can be invoked locally on the mConsole framework by using the getServiceReference and getService standard methods of org.osgi.framework.BundleContext. They will receive remote reference to the corresponding services running on the connected OSGi framework and will be able to manipulate them. In this way you do not have to bother with calling the necessary services yourself.

The following code example illustrates getting the remote User Admin Service and taking a couple of actions through it (namely, adding a new user to the “Administration” group and changing the password of the “admin” user). The remote User Admin will take care of making the User Admin running on the connected OSGi framework perform these actions, in turn.

ServiceReference ref = bc.getServiceReference(RemoteUserAdmin.class.getName());
RemoteUserAdmin userAdm = (RemoteUserAdmin)bc.getService(ref);
userAdm.addBasicMember(“Administration”, “testUser”);
userAdm.changePassword(“admin”,”123456”);
Listing 2: Getting and using the remote implementation of User Admin Service

In addition, the mConsole Remote Services API provides utility packages providing means for obtaining miscellaneous information about the connected framework through the com.prosyst.admin.framework package.

Note: Optionally, you can use directly the PMP API for obtaining remote reference to the services running on the OSGi framework. This is done in the standard way described in the PMP Bundle documentation.


Using the mBS GUI Components (Bundle Tree, Bundle Tabs, etc.)

A plug-in can access MBS connection specific components by implementing and registering as a service the com.prosyst.mc.mbs.bundles.AdditionalGUIFactory interface. Its getInstance method will return new instances each time or one cached instance of the AdditionalGUIInterface implementation. As you may have notices, the pattern for using AdditionalGUIFactory and AdditionalGUIInterface is similar to the pattern for using GUIFactory and GUIInterface.


Creating a Custom Login Service

By creating a custom login service, you can enable mConsole to log in a custom type of servers, and administrate them in a custom way. In summary, you can:

To create a custom login service, you must implement and register as a service the com.prosyst.mc.login.LoginService interface.

In this section, we shall demonstrate the step-by-step creation of a custom login service with name "TEST". It will provide TCP/IP connection to a custom server type. The login properties to be defined by the user in the Login Box are: server host and server port.

For simplicity, our custom server type will be a simple server application listening for client conections on TCP port 3333. You can create one (you just need a standard java.io.ServerSocket listening) or pack in a bundle the code shown in Listing 1.1 of the Connector Service Bundle document.

Implementing a Login Service

Step 1. The first aspect in implementing a login service is defining its login properties. They will be visualised by mConsole's Login Box, and the user will be able to set appropriate values to them.

The login properties of the service can contain any information you need for your custom connections. For example, this could be: host, port, user account, timeout, etc.

Defining the login settings takes two tasks:

Note: The Server Name and Server Type login properties will automatically be added to the settings displayed in the Login Box. You don't have to add them to the login service's configuration.

1.1. Providing a configuration resource. Here, we shall not provide detailed explanations of the principles for using the Configuration Admin. We only present a sample code and configuration XML that enables our "TEST" login service to provide the custom server's host and port as user-configurable login properties.

Listing 3.1.1 shows a sample org.osgi.service.cm.ManagedService implementation that will provide the configuration resource of our TEST login service. It registers a Managed Service with PID: test.login.service.pid. It contains a couple of configuration properties:

import org.osgi.framework.*;
import java.util.Hashtable;
import java.util.Dictionary;
import org.osgi.service.cm.ManagedService;

/*
 * The role of this class is to provide the host and port
 * login properties as configuration resources in the
 * OSGi Configuration Admin.
 */
public class TestLoginManagedService implements ManagedService {
  private ServiceRegistration reg;
  private Hashtable props;
  
  public TestLoginManagedService(BundleContext bc) throws BundleException {
    props = new Hashtable();
    //The PID of the login service's configuration
    props.put(Constants.SERVICE_PID,"test.login.service.pid");
    //The properties contained in the configuration
    props.put("host_ID","HOST");
    props.put("port_ID","PORT");
    bc.registerService("org.osgi.service.cm.ManagedService", this, props);
  }

  public void destroyConfiguration() {
    reg.unregister();
  }
  
  public void updated(Dictionary properties) {
    if(properties==null) {
      reg.setProperties(props);
    } else {
      reg.setProperties(properties);
    }
  }
}
Listing 3.1.1: Implementing a ManagedService that will provide the login settings as configurable properties

And Listing 3.1.2 shows the configuration XML that describes the login service's configuration PID and the host and port properties it contains.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE metatype-provider SYSTEM "conf.dtd">
<metatype-provider> <objectclass load="true"> <locale>en</locale> <name>Test Login Service Configuration</name> <id>test.login.service.pid</id> <description>Just a simple login service example</description> <attribute modifier="req"> <name>HOST</name> <id>host_ID</id> <description>The server host.</description> <type>&string;</type> <cardinality>0</cardinality> <value> <scalar>localhost</scalar> </value> </attribute> <attribute modifier="req"> <name>PORT</name> <id>port_ID</id> <description>The server port.</description> <type>&string;</type> <cardinality>0</cardinality> <value> <scalar>3333</scalar> </value> </attribute> </objectclass> </metatype-provider>
Listing 3.1.2: The configuration XML describing the login properties

1.2. Returning the ObjectClassDefinition through the getLoginProperties() method implementation. Implement the getLoginProperties() method so as to obtain the ObjectClassDefinition created by the MetaTypeProvider of your login service configuration. This method will be called internally by mConsole to get the appropriate login properties and visualize them in the Login Box when this type of server is selected from the Server Type combo box.

  public ObjectClassDefinition getLoginProperties() {
    try {
      //Calling the MetaDataManager to provide the necessary ObjectClassDefinition
      ServiceReference ref = bc.getServiceReference(MetaDataManager.class.getName());
if (ref == null) {
return null;
}

MetaDataManager mdManager = (MetaDataManager)bc.getService(ref);
return mdManager.getMetaTypeProvider("test.login.service.pid"). getObjectClassDefinition(null, null);

} catch (IOException e) {
return null;
}
}
Listing 3.2: Returning the ObjectClassDefinition from the login properties

Step 2. Establishing/closing connections to your custom server type. The connecting/disconnecting mechanism of the custom login service is the next essential aspect that must be implemented. The connection logic of login services is defined through the implementation of the connect(Dictionary properties) method of the LoginService interface. This method is called internally by mConsole when the user selects a server of the custom type from the Login Box, and presses the Connect button. It establishes physical connection to the specified server. The properties parameter contains the configuration properties defined by our login configuration resource, but the ObjectClassDefinition is transformed into a java.util.Dictionary object, with contents in a <property> - <value> form. The <property> part will reflect the ID of the configuration property, not the property name.

The connect method returns a boolean indicating if the connection is successful or not.

In the case with our TEST login service, we need to get the values of the two properties we defined in the service configuration: the "host_ID", representing the server host, and the "port_ID", representing the port. On the basis of the host and port information, we shall create a TCP/IP connection to the user-specified server. We shall pass a socket URI to the Connector Service, and it will create a TCP connection to the specified server.

  public boolean connect(Dictionary dict) throws LoginException,
                                                 IllegalArgumentException {
   
    //Getting the values of the host and port properties  
    String host = (String) dict.get("host_ID");
    String port = (String) dict.get("port_ID"); 
      
    //Getting the Connector Service to create the TCP connection
    connRef = bc.getServiceReference(ConnectorService.class.getName());
    if(connRef != null) {
      connector = (ConnectorService) bc.getService(connRef);
      
      try {
        //the socket stream connection to the specified host and port
        connection = (StreamConnection)connector.open("socket://" +
                                                       host + ":" +
                                                              port);
        DataOutputStream outputStream = connection.openDataOutputStream();
        . . .//do something with the obtained connection
        return true; //we return true to show the connection was successful      

      } catch(java.io.IOException ioe) {
        ioe.printStackTrace();
        return false; //in case of exception, we return false
      }
      
    }  
    
    else {
      //If the Connector Service is unavailable, we cannot establish connection
      return false;
    }
  }
Listing 3.3.1: Implementing the connect method of the Login Service

The disconnect() method closes the established connection. Make sure your implementation of this method closes the connection properly.

The closing of the connection for our TEST login service is an easy task. It is illustrated in Listing 3.3.2.

  public void disconnect(Dictionary dictionary) throws LoginException {
try {
connection.close();
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
Listing 3.3.2: Implementing the disconnect method of the Login Service

When a problem occurs during connection and login, a LoginException is thrown.

2.1. Providing connection events to interested listeners and mConsole itself. If you want to notify mConsole plug-ins about changes in connections' status, you must distribute events in the implementation of the connect method. You can send events when the connection is established or broken, and while the connection establishment is in progress.

Connection events are represented by com.prosyst.mc.login.ConnectionEvent objects. A connection event can be:

The most interesting connection event type of the three is the ConnectionEvent.CONNECTING. Issuing CONNECTING events periodically during the login process, and updating the information about the percentage of successful establishment, allows mConsole to display messages about the current operation it performs and percentage elapsed in the login progress bar.

Listing 3.4 shows re-writing the connect method by creating and sending connection events when necessary.

public boolean connect(Dictionary dict) throws LoginException, IllegalArgumentException {
  . . .
  //When the Connector Service was successfully obtained
  ConnectionEvent conn10percent = new ConnectionEvent(ConnectionEvent.CONNECTING, 
                                                                    serverType,
                                            "Getting the Connector Service...",
                                                                           10);
     sendEvent(conn10percent);
      
     . . .//After the connection has been opened successfully
     ConnectionEvent conn40percent = new ConnectionEvent(ConnectionEvent.CONNECTING,
                                                                        serverType,
                                                       "Opening the connection...",
                                                                               40);
        sendEvent(conn40percent);
        
        . . .//After the greeting has been sent to the server
        ConnectionEvent conn70percent = new ConnectionEvent(ConnectionEvent.CONNECTING,
                                                                        serverType,
                                               "Sending greeting to the server...",
                                                                               40);
        sendEvent(conn70percent);    
  
        . . .//When all phases of the connection establishment have passed
        ConnectionEvent connected = new ConnectionEvent(ConnectionEvent.CONNECTED,
                                                                       serverType,
                                         "Connection established successfully...",
                                                                             100);
        sendEvent(connected);
  . . .
}
Listing 3.4: Sending events

Step 3. Providing the GUI components for the connection to your custom server type. For each established connection, mConsole loads only the GUI components specified by the login service responsible for this type of connection. Defining the GUI components for a specific server type is done by implementing the getGUINames() method of the LoginService interface. This method returns as String[] the names of the GUI interfaces to be loaded. It is called internally by the Visual Container service on each connection to a server of this type.

The mechanism of providing the GUI interface names is up to the needs of your custom implementation. For example, you may provide the names statically, by hard-coding them in the body of the getGUINames() method. Or, you can provide dynamic retrieval of the names by reading a configuration resource from the OSGi Configuration Admin (similarly to the retrieval of login properties demonstrated in Step 1). The latter method would be useful if you need flexible mechanism of GUI loading.

Note: When you add a GUI interface name to the array returned by getGUINames(), make sure there is an implementation of GUI interface with the same name in the mConsole framework.

Listing 3.5 demonstrates very simple implementation of the getGUINames() method. It returns the name of the GUI interface we created in part Creating Basic GUI Components.

  public String[] getGUINames() {
return new String[] {"Demo Console Tab"};

}
Listing 3.5: Providing the GUI components for this login service through the getGUINames() method

Registering the Login Service

After implementing a login service, register the LoginService interface implementation as a service in the framework. This is necessary for mConsole to recognize the new server type and take care of visualizing its proper login settings, and call its methods when necessary.

The login service must be registered with at least the LoginService.CONNECTION_TYPE property. Its value must have the display name of your custom login type (the name that will appear in the Server Types combo box).

    Hashtable props = new Hashtable();
props.put(LoginService.CONNECTION_TYPE, "TEST");
reg = bc.registerService(LoginService.class.getName(), this, props);
myManagedService = new TestLoginManagedService(bc);
Listing 3.6: Registering the login service


Figure 3.1: The custom login service appears in the Server Type drop-down list of the Login Box


Figure 3.2: TEST server settings in the Login Box

Registering a ConnectionListener

Through connection listeners you can register for receiving connection events. The addConnectionListener(ConnectionListener) and removeConnectionListener(ConnectionListener) methods register/unregister a connection listener respectively.

When you register your custom login service, mConsole automatically registers a service listener for it. Once the login service is registered, it is visualized in the login box. If the service becomes unregistered, the login box stops displaying it.

If you try to establish connection with a server with type your custom login service, then mConsole will also register a connection listener to the login service.

If you want to add a custom connection listener to a login service, you need to:

import com.prosyst.mc.login.ConnectionEvent;
import com.prosyst.mc.login.ConnectionListener;

public class TestConnectionListener implements ConnectionListener {

  public void eventOccurred(ConnectionEvent ce) {
    
    System.out.println("[Test connection listener] A new event of type: " + 
                                                        ce.getEventType() + 
                                            " is received.\n Details:\n " + 
                                                            "Server type" +
                                                ce.getServerType() + "\n" +
                                              "Message: " + ce.getMessage()
      );
    
  }
}
Listing 3.7: Creating a ConnectionListener

As there are a number of login services in the mConsole container, you need to be sure you have obtained the right service instance. This is done by applying an LDAP search filter LoginService.CONNECTION_TYPE equal to the value of the service registration property with the same name (see Registering the Login Service).

import org.osgi.framework.*;
import com.prosyst.mc.login.LoginService;
import com.prosyst.mc.login.ConnectionListener;

. . .
  private BundleContext bc;
  private ServiceReference[] sRef;
  private LoginService testLoginService;
  /*The LDAP filter for retrieving the right login service instance*/
  private String filter = "(" + LoginService.CONNECTION_TYPE + "TEST)";
  private TestConnectionListener testConnListener;

  . . .
  /*Getting the login services corresponding to the filter*/
  sRef = bc.getServiceReferences(LoginService.class.getName(), filter);
  if(sRef !=null) {
    for (int i = 0; i < sRef.length; i++) {
      testLoginService = (LoginService) bc.getService(sRef[i]);
    
      //Adding the connection listener
      testConnListener = new TestConnectionListener();
      testLoginService.addConnectionListener(testConnListener);
    }
  }

Figure 3.8: Registering the ConnectionListener


Creating a Custom Property Editor

A property editor allows editing configuration properties of bundles available on the connected OSGi framework. mConsole allows dynamic loading of property editors for bundles. There are two types of editors: general and custom. The general property editor is automatically created by mConsole and you do not need to write it yourself. However, if you want your bundle to have a custom editor, you do need to write it.

Both types of editors are represented by the com.prosyst.mc.GUIFactory service interface. For general property editors, the service is automatically registered by mConsole with a property: EditorGUIInterface.GENERAL_TYPE. Custom editors must be created by the developer and must be registered with the EditorGUIInterface.CUSTOM_TYPE property.

Each time a configuration node from the tree is clicked, mConsole checks if there is a GUI Factory Service registered with the EditorGUIInterface.CUSTOM_TYPE or EditorGUIInterface.GENERAL_TYPE property. FIRST mConsole checks for CUSTOM editor services, and if there aren't any, checks for an available GENERAL editor service. By default, the general editor service is exported by the osgieditors.jar bundle. If the bundle is has been stopped or uninstalled, the general property editor will be unavailable and only ProSyst's icon will be displayed on selecting the configuration node from the tree.

Framework Bundle Prerequisites

A bundle having a property editor must correspond to the following conditions:

  1. If the bundle contains a single configuration, its manifest must contain the Config attribute with value either
    xml=<xml_resource_location>; pid=<service_pid>; name=<display_name>;
    or simply
    name=<display_name>;

    If the bundle contains a factory configuration, Its manifest must contain the FactoryConfig attribute. Its value can be one of the following:
    xml=<xml_resource_location>; pid=<service_fpid>; name=<display_name>;
    or simply
    name=<display_name>;

  2. If you want to have a general property editor, you do not need to write any custom component for the mConsole framework. mConsole will automatically generate a property editor for it.
  3. If you want a special property editor for the bundle, you must create a custom component which will be started on mConsole and will export a GUI Factory service registered with the property: EditorGUIInterface.CUSTOM_TYPE.

Writing the Bundle

The source code of the bundle itself does not imply the availability or absence of a property editor of any type. Usually, bundles with property editors use the OSGi Configuration Admin Service for storing bundle configurations, but this is NOT required. For example, the ProSyst User Admin Bundle owns a custom property editor for managing users and groups, but the information is not stored in the OSGi Configuration Admin Service.

The following example is taken from the sources of the Property Editor Demo (demo/framework/mc). The JAR file of this demo bundle is demo/bundles/editordemo.jar. It stores a simple set of properties with the OSGi Configuration Admin Service.

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ConfigurationException;

/**
 * This class is responsible for creating  
 * and updating the configuration of phone book.
 * The configuration will be viewed on Managment Console custom property editor.
 */
public class EditorDemoActivator implements BundleActivator, ManagedService {
  
  /**
   * Service PID for this managed service.
   */
  public static final String pid = "demo.mc.pid";
  // This field contains phone book content.It is a sequence of name, telephone pairs.
  private String[] addresses;
  // The bundle context object of this bundle.
  private BundleContext bc;
  private ServiceRegistration      managedServiceReg;
  // Contains properties for this ManagedService.
  private Hashtable props;
  
  /**
   * Starts the bundle. Registers the ManagedService service.
   *
   * @param   bc  the BundleContext of this bundle 
   * @exception   BundleException if an error occurs  
   */
  public void start(BundleContext bc) throws BundleException {
    try {
      this.bc = bc;
      props = new Hashtable(1, 1);
      props.put("service.pid", pid);  
      managedServiceReg = bc.registerService(ManagedService.class.getName(), this, props);
    } catch (Exception e) {
      throw new BundleException(e.getMessage(), e);
    }  
  }

  /**
   * Stops the bundle. Unregisters the ManagedService service.
   *
   * @param   bc  the BundleContext of this bundle 
   * @exception   BundleException if an error occurs  
   */
  public void stop(BundleContext bc) throws BundleException {
    try {
      managedServiceReg.unregister();
      managedServiceReg = null;
    } catch (Exception e) {
      throw new BundleException(e.getMessage(), e);
    }  
  }
    
  /**
   * Updates phone book configuration.
   * @param  properties configuration properties, or null
   * @throws ConfigurationException when the update fails
   */
  public void updated(Dictionary properties) throws ConfigurationException {
    if (properties != null) {
      addresses = (String[])properties.get("phonebook");
    }
  }
}
Listing 4.1: Creating the bundle

The manifest of this bundle is:

Manifest-Version: 1.0
Bundle-Vendor: ProSyst
Bundle-Version: 1.0
Bundle-Category: demo
Bundle-Activator: demo.mc.bundle.EditorDemoActivator
Bundle-Name: Editor Demo Bundle
Bundle-Description: 
Import-Package: org.osgi.service.cm; specification-version="1.0"
Config: xml=/demo/mc/bundle/config.xml; pid=demo.mc.pid; name=Editor Demo
Listing 4.2: The manifest of the bundle

Writing the Property Editor

We'll create a custom property editor for the above bundle. It is packed in the demo/bundles/preditor.jar file. It will be installed and started on the mConsole framework.

...//imports

public class DemoActivator implements BundleActivator, ServiceListener, GUIFactory {
  
  // Holds the display name of the editor.
  public static final String EDITOR_NAME = "Editor Demo";
  
  ....
  
  public DemoActivator() {
    //holds the obtained property editors
    guiHash = new Hashtable(2); 
    //caches the property editors which are not currently in use 
    guiVector = new Vector(2, 1);
    
    props = new Hashtable(3);//the service properties
    props.put(GUIFactory.GUI_NAME, EDITOR_NAME);
    props.put(EditorGUIInterface.EDITOR_TYPE, EditorGUIInterface.CUSTOM_TYPE);
  }
  
  ......
  
  public void start(BundleContext bc) throws BundleException {
    this.bc = bc;
    try {
      bc.addServiceListener(this, "((objectClass=" + VisualContainer.class.getName() +
        ")(objectClass=" + RemoteConfigAdmin.class.getName() + "))");
    } catch (InvalidSyntaxException exc) {
      // Nothing to do, The syntax is right.
    }    
    containerRef = bc.getServiceReference(VisualContainer.class.getName());
    if (containerRef != null) {
      container = (VisualContainer) bc.getService(containerRef);
    }    
   //registers the GUI Factory service
   guiRegistration = bc.registerService(GUIFactory.class.getName(), this, props);
  } 
  
  .....
  
  /**
* Creates new GUI Interface instance according to the
* specified server type property. Menus, toolbars and
* GUI components must be different for the different
* instances.
*
* @param serverType MBS or MPRM connection type.
* @return new instance of the GUI Interface.
*/
public GUIInterface getInstance(String serverType) { //if the container is not registered, //or if the RemoteConfigAdmin service is not registered //with the specified server type, this method will return null if (container == null) { return null; } try { String filter = "(server_type=" + serverType + ')'; ServiceReference[] refs=bc.getServiceReferences(RemoteConfigAdmin.class.getName(),filter); if (refs == null || refs.length == 0) { return null; } RemoteConfigAdmin configAdmin = (RemoteConfigAdmin) bc.getService(refs[0]); EditorPanelImpl editorPanel; if (guiVector.size() == 0) { editorPanel=new EditorPanelImpl(this,serverType,container.getParentFrame(),configAdmin); } else { editorPanel = (EditorPanelImpl) guiVector.elementAt(0); guiVector.removeElementAt(0); editorPanel.reset(this, serverType, container.getParentFrame(), configAdmin); } guiHash.put(serverType, editorPanel); return editorPanel; } catch (Exception exc) { return null; } } ..... }
Listing 4.3: Writing the property editor

The full source code of the property editor is available in the demo/framework/mc/demo/mc/editor folder.

If we do not activate the custom property editor (preditor.jar) on the mConsole, the general property editor will be loaded, which looks like this:


Figure 4.1: The general property editor for the demo bundle. It appears if we haven't started its custom property editor.

If we activate the custom property editor, however, it will look like this:


Figure 4.2: The custom property editor for the demo


Deploying a Plug-in for MBS Servers

If you want the GUI components of your plug-in to be loaded by the Visual Container service when mConsole is connected to an MBS server, you need to do the following prerequisites:

  1. Edit the “GUI Interface names array” property of the mBS Login Service adding the name of the GUI plug-in (corresponding to the value of the GUI_NAME registration property) to the list of GUI interfaces.
  2. Install and start the plug-in as a bundle on the mConsole container framework.
  3. Disconnect from the mConsole container and connect to an OSGi framework. You should observe your plug-in's components appear in mConsole.

Editing the “GUI Interface names array” Property of the mBS Login Service

The “GUI Interface names array” property of the mBS Login Service determines the GUIInterface components that will be displayed when mConsole is connected to an OSGi framework over “MBS” connection type. Hence, you need to add the name (corresponding to the GUI_NAME registration property of the plug-in) of the GUI component to the array of GUI names available in the mBS Login Service.

This is done with the following steps:

  1. Log in the MC framework. This is done by selecting “Server Type: MC” from the login dialog, and pressing Connect.
  2. Unfold the “mc” node from the bundle tree in the left pane. The mBS Login Bundle along with other bundles available in the same category should be visible in the category node from the tree.
  3. Unfold the mBS Login Bundle. The “mBS Admin Configuration” node should appear.
  4. Select the “mBS Admin Configuration” node. The property editor of the mBS Login Service should appear in the right pane.
  5. Press the Add button that is located below the “GUI Interface names array” property.
  6. In the blank text fiend that appears at the bottom of the list of GUI names, type the name of your GUI component.
  7. When ready, press the Set button, placed at the bottom of the property editor.


Figure 5: Adding the name of the GUI component to the “GUI Interface names array” property

If everything goes well, the next time you log in an MBS server you should see the demo's menu, toolbar and main component (tab) appear in mConsole (like in Figure 6.1).


Figure 6.1: GUI components added by the demo plug-in

Using the GUI Plugin Demo

When you select the Demo Menu -> Hello command or press the button, the following greeting message appears:


Figure 6.2: The message sent by the demo plug-in when the Demo Menu -> Hello command is invoked

If you select the Demo Menu -> Progress Demo command or press the button, you will see a progressive operation simulated by the plug-in:


Figure 6.3: Progress bar of the demo

When the progressing operation finishes successfully, you should see the following message:


Figure 6.4: The message dialog that you see after the progress demo finishes


mConsole