11. Example: Writing a Custom Java Client

As alluded to in Chapter 1, Understanding How Scripts Work with the JBoss ON Server and CLI, the clients in JBoss ON use either the JBoss Remoting framework or the JBoss ON remote APIs to access server functionality. The JBoss ON CLI is essentially a Java skin over the remote API. Any application written in Java or a JVM-compatible language can access the JBoss ON remote API.
This example creates an LDAP integration for LDAP group-based authorization for JBoss ON. The sample Java class pulls in the authorization and search classes from the JBoss ON API, and then the script starts a simple synchronization service that maps the LDAP groups and users to the JBoss ON roles and users.

NOTE

LDAP-based group authorization is already configured in JBoss ON. This client is simply used as an example to show how a remote Java client can interact with the JBoss ON server.

11.1. Creating SampleLdapClientMain.class

This Java class uses the JBoss ON API for users, permissions, roles, and searching and sorting resource entries. The class then sets up a mapping between the LDAP database and the JBoss ON database, so that the user and role information in each is synchronized.
The JBoss ON CLI exposes a number of libraries, including domain classes for searching and remote classes for handling resources. The SampleLdapClientMain.java file requires these remote client JARs to be in its classpath:
  • cliRoot/rhq-remoting-cli-3.1.2.GA/lib/rhq-remoting-client-api-4.4.0-SNAPSHOT.jar
  • cliRoot/rhq-remoting-cli-3.1.2.GA/lib/rhq-core-domain-4.4.0-SNAPSHOT.jar
  • cliRoot/rhq-remoting-cli-3.1.2.GA/lib/persistence-api-1.0.jar
  • cliRoot/rhq-remoting-cli-3.1.2.GA/lib/rhq-enterprise-server-4.4.0-SNAPSHOT-client.jar
  • cliRoot/rhq-remoting-cli-3.1.2.GA/lib/hibernate-annotations-3.2.1.GA.jar
Example 1, “SampleLdapClientMain.java” is annotated to show what each step of the class is doing.

Example 1. SampleLdapClientMain.java

package org.rhq.sample.client.java.ldap;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.authz.Role;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.criteria.ResourceGroupCriteria;
import org.rhq.core.domain.criteria.RoleCriteria;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.core.domain.util.PageList;
import org.rhq.enterprise.clientapi.RemoteClient;
import org.rhq.enterprise.server.auth.SubjectManagerRemote;
import org.rhq.enterprise.server.authz.RoleManagerRemote;
import org.rhq.enterprise.server.resource.ResourceManagerRemote;
import org.rhq.enterprise.server.resource.group.ResourceGroupManagerRemote;

/**
 * This sample program utilizes the RHQ Remote API via a Java Client.
 * 
 * The RHQ CLI is the preferred remote client approach for script-based clients.  Programmatic Java clients
 * can utilize the Remote API via the same mechanism used by the CLI, making use of ClientMain object, as
 * done in this sample.  This is the recommended mechanism although a remote Java client could also use the 
 * remote API exposed as WebServices. 
 * 
 * @author Jay Shaughnessy
 */
public class SampleLdapClientMain {
    // A remote session always starts with a login, define default user/password/server/port
    private static String username = "rhqadmin";
    private static String password = "rhqadmin";
    private static String host = "localhost";
    private static int port = 7080;

    /**
     * This is a standalone remote client but calls to the remote API could be embedded into another application.
     */
    public static void main(String[] args) {
        if (args.length > 0) {
            if ((args.length != 2) && (args.length != 4)) {
                System.out
                    .println("\nUsage: SampleLdapClientMain [ [ username password ] | [username password host port] ]");
                System.out.println("\n\nDefault credentials: rhqadmin/rhqadmin");
                System.out.println("\n\nDefault host: determined from wsconsume of WSDL");
                return;
            } else {
                username = args[0];
                password = args[1];

                if (args.length == 4) {
                    host = args[2];
                    port = Integer.valueOf(args[3]);
                }
            }
        }

        LdapClient ldapClient = null;

        try {
            ldapClient = new LdapClient();
            ldapClient.synchLdapJbasManagers();

        } catch (Throwable t) {
            System.out.println("Error: " + t);
            t.printStackTrace();
        } finally {
            if (null != ldapClient) {
                // clean up the session by logging out from the RHQ server
                ldapClient.logout();
            }
        }
    }

    /**
     * The LdapClient interacts with the RHQ Server to help synchronize a (fake) LDAP server with RHQ.  
     */
    public static class LdapClient {
        // group containing all jbas resources
        private static final String JBAS_GROUP = "jbas-resource-group";

        // role for jbas managers
        private static final String JBAS_MANAGER_ROLE = "jbas-manager-role";

        // the users that should be assigned the JBAS_MANAGER_ROLE    
        private static final List<String> JBAS_MANAGERS = new ArrayList<String>();

        // the prmissions that should be assigned the JBAS_MANAGER_ROLE
        private static final Set<Permission> JBAS_MANAGER_PERMISSIONS = new HashSet<Permission>();

        // jbas AS Server resource type (note, this picks up AS4 and AS5 resources as they share the same type name)
        private static final String JBAS_SERVER_NAME = "JBossAS Server";

        /* The Remote API offers different remote "managers" roughly broken down by subsystem/function
         * Below are the managers needed by this client, there are several others that offer
         * interfaces into areas such as operations, alerting, content, etc. See the API. 
         */
        private ResourceGroupManagerRemote resourceGroupManager;
        private ResourceManagerRemote resourceManager;
        private RoleManagerRemote roleManager;
        private SubjectManagerRemote subjectManager;

        /* This represents the RHQ user that is logged in and making the remote calls. This user must
         * already exist.  For the work being done here the user must also have SECURITY_MANAGER permissions.
         */
        private Subject subject;

        /* This is the object through which we access the remote API */
        private RemoteClient remoteClient;

        static {
            // add some fake users since we're not actually hooked into an ldap server
            JBAS_MANAGERS.add("mgr-1");
            JBAS_MANAGERS.add("mgr-2");

            // add some permissions since we're not actually hooked into an ldap server
            JBAS_MANAGER_PERMISSIONS.addAll(Permission.RESOURCE_ALL);
        }

        public LdapClient() throws Exception {
            this.remoteClient = new RemoteClient(null, host, port);
            this.subject = remoteClient.login(username, password);

            this.resourceGroupManager = this.remoteClient.getResourceGroupManager();
            this.resourceManager = this.remoteClient.getResourceManager();
            this.roleManager = this.remoteClient.getRoleManager();
            this.subjectManager = this.remoteClient.getSubjectManager();
        }

        /*
         * This method simulates a sync between an Ldap server that has defined a group of JBAS managers
         * and wants to associate them with a role allowing jbas management.  Meaning, a role that 
         * has the proper permissions and is associated with the jbas resources.
         */
        private void synchLdapJbasManagers() throws Exception {

            // create the jbas manager role if necessary            
            // use a criteria search with a name filter to look for the role
            RoleCriteria roleCriteria = new RoleCriteria();
            roleCriteria.addFilterName(JBAS_MANAGER_ROLE);
            PageList<Role> jbasManagerRoles = roleManager.findRolesByCriteria(subject, roleCriteria);
            Role jbasManagerRole;
            if (1 == jbasManagerRoles.size()) {
                jbasManagerRole = jbasManagerRoles.get(0);
            } else {
                // if it doesn't exist, create it
                jbasManagerRole = new Role(JBAS_MANAGER_ROLE);
                jbasManagerRole = roleManager.createRole(subject, jbasManagerRole);
            }
            // ensure the proper permissions are granted to the role by using an update
            jbasManagerRole.setPermissions(JBAS_MANAGER_PERMISSIONS);
            roleManager.updateRole(subject, jbasManagerRole);

            // create, populate and associate the jbas group if necessary
            ResourceGroupCriteria resourceGroupCriteria = new ResourceGroupCriteria();
            resourceGroupCriteria.addFilterName(JBAS_GROUP);
            PageList<ResourceGroup> jbasGroups = resourceGroupManager.findResourceGroupsByCriteria(subject,
                resourceGroupCriteria);
            ResourceGroup jbasGroup;
            if (1 == jbasGroups.size()) {
                jbasGroup = jbasGroups.get(0);
            } else {
                jbasGroup = new ResourceGroup(JBAS_GROUP);
                jbasGroup = resourceGroupManager.createResourceGroup(subject, jbasGroup);
                // Ensure the group is recursive to make all the children available.
                // In this case a specific method is available, so a general update call is not needed.
                resourceGroupManager.setRecursive(subject, jbasGroup.getId(), true);
            }

            // Now find all of the JBAS server resources by adding a criteria filter on resource type name
            ResourceCriteria resourceCriteria = new ResourceCriteria();
            resourceCriteria.addFilterResourceTypeName(JBAS_SERVER_NAME);
            PageList<Resource> jbasServers = resourceManager.findResourcesByCriteria(subject, resourceCriteria);
            if (!jbasServers.isEmpty()) {
                int[] jbasServerIds = new int[jbasServers.size()];
                int i = 0;
                for (Resource jbasServer : jbasServers) {
                    jbasServerIds[i++] = jbasServer.getId();
                }

                // ..and add them to the group which will be associated with the manager role
                resourceGroupManager.addResourcesToGroup(subject, jbasGroup.getId(), jbasServerIds);
            }

            // Now, associate the mixed group of Jbas servers to the manager role  
            roleManager.addResourceGroupsToRole(subject, jbasManagerRole.getId(), new int[] { jbasGroup.getId() });

            // sync managers with the role
            // 1. remove obsolete managers
            roleCriteria = new RoleCriteria();
            roleCriteria.addFilterId(jbasManagerRole.getId());
            // add a fetch criteria to the criteria object to get the optionally returned subjects for the role.
            roleCriteria.fetchSubjects(true);
            jbasManagerRole = roleManager.findRolesByCriteria(subject, roleCriteria).get(0);
            Set<Subject> subjects = jbasManagerRole.getSubjects();
            if ((null != subjects) && !subjects.isEmpty()) {
                for (Subject subject : subjects) {
                    if (!JBAS_MANAGERS.contains(subject.getName())) {
                        roleManager.removeSubjectsFromRole(subject, jbasManagerRole.getId(), new int[] { subject
                            .getId() });
                    }
                }
            }

            // 2. add new managers, create subjects for the managers, if necessary
            Subject jbasManagerSubject;
            for (String jbasManager : JBAS_MANAGERS) {
                jbasManagerSubject = subjectManager.getSubjectByName(jbasManager);
                // add the required fields for a subject, note that we skip credentials since this is 
                // simulating ldap
                if (null == jbasManagerSubject) {
                    jbasManagerSubject = new Subject();
                    jbasManagerSubject.setName(jbasManager);
                    jbasManagerSubject.setEmailAddress("jbas.manager@sample.com");
                    jbasManagerSubject.setFactive(true);
                    jbasManagerSubject.setFsystem(false);
                    jbasManagerSubject = subjectManager.createSubject(subject, jbasManagerSubject);
                }

                // Finally, make sure my current set of jbas managers is associated with the manager role.
                roleManager.addSubjectsToRole(subject, jbasManagerRole.getId(),
                    new int[] { jbasManagerSubject.getId() });
            }
        }

        public void logout() {
            if ((null != subjectManager) && (null != subject)) {
                try {
                    subjectManager.logout(subject);
                } catch (Exception e) {
                    // just suppress the exception, nothing else we can do
                }
            }
        }
    }
}

11.2. Sample LDAP Script

The sample .bat script invokes the custom Java class.
@echo off

rem ===========================================================================
rem RHQ Remote Client LDAP Example Startup Script
rem
rem The following variables must be set
rem
rem RHQ_CLIENT_HOME    The home directory of the RHQ Client Installation.  The
rem                    RHQ Client can be downloaded from the RHQ GUI under
rem                    the Administration->Downloads menu.
rem ===========================================================================

rem ----------------------------------------------------------------------
rem Set Environment Variables
rem ----------------------------------------------------------------------
set RHQ_CLIENT_HOME=*MUST BE SET*


rem ----------------------------------------------------------------------
rem Prepare the classpath
rem Add all jar files supplied by the RHQ remote client install
rem ----------------------------------------------------------------------

set CLASSPATH=.
call :append_classpath "%RHQ_CLIENT_HOME%\conf"
for /R "%RHQ_CLIENT_HOME%\lib" %%G in ("*.jar") do (
   call :append_classpath "%%G"
)

rem ----------------------------------------------------------------------
rem Prepare the VM command line options to be passed in
rem ----------------------------------------------------------------------

if not defined RHQ_CLIENT_JAVA_OPTS (
   set RHQ_CLIENT_JAVA_OPTS=-Xms64m -Xmx128m -Djava.net.preferIPv4Stack=true
)

rem ----------------------------------------------------------------------
rem Uncomment For debugging on port 9999
rem ----------------------------------------------------------------------

rem set RHQ_CLIENT_ADDITIONAL_JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,address=9999,server=y,suspend=y


rem ----------------------------------------------------------------------
rem Execute the VM which starts the CLIENT
rem ----------------------------------------------------------------------

set CMD="%JAVA_HOME%\bin\java.exe" %RHQ_CLIENT_JAVA_OPTS% %RHQ_CLIENT_ADDITIONAL_JAVA_OPTS% -cp "%CLASSPATH%" org.rhq.sample.client.java.ldap.SampleLdapClientMain %RHQ_CLIENT_CMDLINE_OPTS% %*

cmd.exe /S /C "%CMD%"

goto :done

rem ----------------------------------------------------------------------
rem CALL subroutine that appends the first argument to CLASSPATH
rem ----------------------------------------------------------------------

:append_classpath
set _entry=%1
if not defined CLASSPATH (
   set CLASSPATH=%_entry:"=%
) else (
   set CLASSPATH=%CLASSPATH%;%_entry:"=%
)
goto :eof

rem ----------------------------------------------------------------------
rem CALL subroutine that exits this script normally
rem ----------------------------------------------------------------------

:done

endlocal

exit /B 0