Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

DRAFT

Summary

This documents the implementation of one operation of the groups API.  There is a reference implementation server which makes it easy to implement the FIFER protocol.

hasMember operation

Note: this is similar to what we already discussed, though as it was implemented, things were tweaked.  Of course we should discuss the discrepancies :)
This is an outline of the object model.  Each FIFER transport (REST, POX, POJ, SOAP) uses this object model.

...

  • GroupHasMemberResponse
    • hasMember
    • responseMetadata: ResponseMetadata
      • errorText: String
      • protocolVersion: String
      • requestId: String
      • resultCode: Sring
      • serverMillis: int
      • serverType: String
      • serverVersion: String
      • success: boolean
      • warnings: String

SOAP

This is the CXF WSDL from the service interface

Code Block
<?xml version='1.0' encoding='UTF-8'?>
<wsdl:definitions name="fiferService_v0_1"
    targetNamespace="http://ws_v0_1.fiferServer.fifer.jasig.org/"
    xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://ws_v0_1.fiferServer.fifer.jasig.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <wsdl:types>
        <xs:schema elementFormDefault="unqualified"
            targetNamespace="http://ws_v0_1.fiferServer.fifer.jasig.org/"
            version="1.0" xmlns:tns="http://ws_v0_1.fiferServer.fifer.jasig.org/"
            xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xs:element name="hasMember" type="tns:hasMember" />
            <xs:element name="hasMemberResponse" type="tns:hasMemberResponse" />
            <xs:complexType name="hasMember">
                <xs:sequence>
                    <xs:element minOccurs="0" name="groupLookup" type="tns:groupLookup" />
                    <xs:element minOccurs="0" name="subjectLookup" type="tns:subjectLookup" />
                    <xs:element minOccurs="0" name="immediacy" type="xs:string" />
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="groupLookup">
                <xs:sequence>
                    <xs:element minOccurs="0" name="handle" type="xs:string" />
                    <xs:element minOccurs="0" name="id" type="xs:string" />
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="subjectLookup">
                <xs:sequence>
                    <xs:element minOccurs="0" name="handle" type="xs:string" />
                    <xs:element minOccurs="0" name="id" type="xs:string" />
                    <xs:element minOccurs="0" name="idOrHandle" type="xs:string" />
                    <xs:element minOccurs="0" name="subjectSource" type="xs:string" />
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="hasMemberResponse">
                <xs:sequence>
                    <xs:element minOccurs="0" name="return"
                        type="tns:groupHasMemberResponse" />
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="groupHasMemberResponse">
                <xs:sequence>
                    <xs:element name="hasMember" type="xs:boolean" />
                    <xs:element minOccurs="0" name="responseMetadata" type="tns:responseMetadata" />
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="responseMetadata">
                <xs:sequence>
                    <xs:element minOccurs="0" name="errorText" type="xs:string" />
                    <xs:element minOccurs="0" name="protocolVersion" type="xs:string" />
                    <xs:element minOccurs="0" name="requestId" type="xs:string" />
                    <xs:element minOccurs="0" name="resultCode" type="xs:string" />
                    <xs:element name="serverMillis" type="xs:int" />
                    <xs:element minOccurs="0" name="serverType" type="xs:string" />
                    <xs:element minOccurs="0" name="serverVersion" type="xs:string" />
                    <xs:element name="success" type="xs:boolean" />
                    <xs:element minOccurs="0" name="warnings" type="xs:string" />
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </wsdl:types>
    <wsdl:message name="hasMember">
        <wsdl:part element="tns:hasMember" name="parameters">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="hasMemberResponse">
        <wsdl:part element="tns:hasMemberResponse" name="parameters">
        </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="FiferGroupService">
        <wsdl:operation name="hasMember">
            <wsdl:input message="tns:hasMember" name="hasMember">
            </wsdl:input>
            <wsdl:output message="tns:hasMemberResponse" name="hasMemberResponse">
            </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="fiferService_v0_1SoapBinding" type="tns:FiferGroupService">
        <soap:binding style="document"
            transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="hasMember">
            <soap:operation soapAction="" style="document" />
            <wsdl:input name="hasMember">
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output name="hasMemberResponse">
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="fiferService_v0_1">
        <wsdl:port binding="tns:fiferService_v0_1SoapBinding" name="FiferGroupServiceImplPort">
            <soap:address location="http://localhost:8090/fiferServer/services/group_v0_1" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

REST

This is like POJ/POX but has a simplified request interface

...

Method

...

GET

...

URL

...

/groups/<groupid>/members/<memberid>[?immediacy=<immediacy>]

...

Request Body

...

Sample request:

Code Block

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ns2:hasMember xmlns:ns2="http://ws_v0_1.fiferServer.fifer.jasig.org/">
			<groupLookup>
				<handle>someHandle</handle>
			</groupLookup>
			<subjectLookup>
				<id>someId</id>
			</subjectLookup>
		</ns2:hasMember>
	</soap:Body>
</soap:Envelope>

Sample response:

Code Block

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ns2:hasMemberResponse xmlns:ns2="http://ws_v0_1.fiferServer.fifer.jasig.org/">
			<return>
				<hasMember>true</hasMember>
			</return>
		</ns2:hasMemberResponse>
	</soap:Body>
</soap:Envelope>

REST

This is like POJ/POX but has a simplified request interface

Method

GET

URL

/groups/<groupid>/members/<memberid>[?immediacy=<immediacy>]

Request Body

None

Response:

Code Block

{
  "groupHasMemberResponse":{
    "hasMember":true,
    "responseMetadata":{
      "protocolVersion":"v0.1.0",
      "requestId":"abcd123412345678909",
      "resultCode":"SUCCESS",
      "serverMillis":123,
      "serverType":"FIFER reference server",
      "serverVersion":"v0.5.0",
      "success":true
    }
  }
}

POJ (plain old json)

This is similar to REST, except it doesnt use HTTP methods, and it doesnt use the URL to convey request information (though you could use request params)

Request:

Code Block

{
  "groupHasMemberRequest":{
    "groupLookup":{
      "handle":"someHandle"
    },
    "subjectLookup":{
      "id":"someId"
    }
  }
}

Note: you could also use URL params:  http://url/fiferServer/poj/v0_1_0?operation=groupHasMemberRequest&groupLookup_handle=someHandle&subjectLookup_id=someId

Response:

Code Block

{
  "groupHasMemberResponse":{
    "hasMember":true,
    "responseMetadata":{
      "protocolVersion":"v0.1.0",
      "requestId":"abcd123412345678909",
      "resultCode":"SUCCESS",
      "serverMillis":123,
      "serverType":"FIFER reference server",
      "serverVersion":"v0.5.0",
      "success":true
    }
  }
}

POX (plan old xml)

Request:

Code Block

<groupHasMemberRequest>
  <groupLookup>
    <handle>someHandle</handle>
  </groupLookup>
  <subjectLookup>
    <id>someId</id>
  </subjectLookup>
</groupHasMemberRequest>

Note: you could also use URL params:  http://url/fiferServer/pox/v0_1_0?operation=groupHasMemberRequest&groupLookup_handle=someHandle&subjectLookup_id=someId

Response:

Code Block

<groupHasMemberResponse>
  <responseMetadata>
    <serverType>FIFER reference server</serverType>
    <requestId>abcd123412345678909</requestId>
    <serverVersion>v0.5.0</serverVersion>
    <protocolVersion>v0.1.0</protocolVersion>
    <success>true</success>
    <resultCode>SUCCESS</resultCode>
    <serverMillis>123</serverMillis>
  </responseMetadata>
  <hasMember>true</hasMember>
</groupHasMemberResponse>

Reference server

This is a Java server that can easily be used to implement a back-end groups service for all of the above transports.  All you need to do is implement the service interfaces.  e.g.

Code Block

/**/**
 *
 */
package org.jasig.fifer.fiferServer.logic;

import org.jasig.fifer.fiferServer.coreapi.GroupHasMemberResponse;
import org.jasig.fifer.fiferServer.coreapi.GroupLookup;
import org.jasig.fifer.fiferServer.coreapi.SubjectLookup;
import org.jasig.fifer.fiferServer.group.MembershipImmediacy;


/**
 * Implement this interface to delegate the fifer logic calls
 * @author mchyzer
 *
 */
public interface FiferGroupLogicInterface {

  /**
   * see if a group has a member
   * @param groupLookup
   * @param subjectLookup
   * @param membershipImmediacy
   * @return the result
   */
  public GroupHasMemberResponse hasMember(GroupLookup groupLookup, SubjectLookup subjectLookup,
      MembershipImmediacy membershipImmediacy);

}

Here is an example of the Grouper implementation (translates the FIFER API to the Grouper client and back):

Code Block

package org.jasig.fifer.fiferServer.grouper;

import org.apache.commons.lang.StringUtils;
import org.jasig.fifer.fiferServer.coreapi.GroupHasMemberResponse;
import org.jasig.fifer.fiferServer.coreapi.GroupLookup;
import org.jasig.fifer.fiferServer.coreapi.ResponseMetadata;
import org.jasig.fifer.fiferServer.coreapi.SubjectLookup;
import org.jasig.fifer.fiferServer.group.MembershipImmediacy;
import org.jasig.fifer.fiferServer.logic.FiferGroupLogicInterface;

import edu.internet2.middleware.grouperClient.api.GcHasMember;
import edu.internet2.middleware.grouperClient.ws.WsMemberFilter;
import edu.internet2.middleware.grouperClient.ws.beans.WsHasMemberResults;
import edu.internet2.middleware.grouperClient.ws.beans.WsSubjectLookup;

/**
 * Implement the fifer group service for Grouper
 * @author mchyzer
 *
 */
public class GrouperGroupLogic implements FiferGroupLogicInterface {

  /**
   * convert from handle to group name
   * @param handle
   * @return the group name
   */
  private String convertFromGroupHandleToGroupName(String handle) {
    //TODO translate from handle to group name
    return handle;
  }

  /**
   * @see FiferGroupLogicInterface#hasMember(GroupLookup, SubjectLookup, MembershipImmediacy)
   */
  @Override
  public GroupHasMemberResponse hasMember(GroupLookup groupLookup, SubjectLookup subjectLookup,
      MembershipImmediacy membershipImmediacy) {

    GcHasMember gcHasMember = new GcHasMember();

    {
      if (!StringUtils.isBlank(groupLookup.getId())) {
        gcHasMember.assignGroupUuid(groupLookup.getId());
      }
      if (!StringUtils.isBlank(groupLookup.getHandle())) {
        gcHasMember.assignGroupName(convertFromGroupHandleToGroupName(groupLookup.getHandle()));
      }

    }

    {
      WsSubjectLookup wsSubjectLookup = new WsSubjectLookup();
      if (!StringUtils.isBlank(subjectLookup.getHandle())) {
        wsSubjectLookup.setSubjectIdentifier(subjectLookup.getHandle());
      }
      if (!StringUtils.isBlank(subjectLookup.getId())) {
        wsSubjectLookup.setSubjectId(subjectLookup.getId());
      }
      if (!StringUtils.isBlank(subjectLookup.getIdOrHandle())) {
        //set both of these for id or identifier
        wsSubjectLookup.setSubjectId(subjectLookup.getIdOrHandle());
        wsSubjectLookup.setSubjectIdentifier(subjectLookup.getIdOrHandle());
      }

    }

    {

      if (membershipImmediacy == MembershipImmediacy.any) {
        gcHasMember.assignMemberFilter(WsMemberFilter.All);
      }
      if (membershipImmediacy == MembershipImmediacy.immediate) {
        gcHasMember.assignMemberFilter(WsMemberFilter.Immediate);
      }
      if (membershipImmediacy == MembershipImmediacy.nonimmediate) {
        gcHasMember.assignMemberFilter(WsMemberFilter.NonImmediate);
      }
    }

    WsHasMemberResults wsHasMemberResults = gcHasMember.execute();

    GroupHasMemberResponse groupHasMemberResponse = new GroupHasMemberResponse();
    ResponseMetadata responseMetadata = new ResponseMetadata();
    groupHasMemberResponse.setResponseMetadata(responseMetadata);


    if (StringUtils.equals(wsHasMemberResults.getResultMetadata().getSuccess(), "T")
        || StringUtils.equals(wsHasMemberResults.getResultMetadata().getResultCode(), "SUBJECT_NOT_FOUND")) {
      responseMetadata.setSuccess(true);

      if (StringUtils.equals(wsHasMemberResults.getResultMetadata().getResultCode(), "SUBJECT_NOT_FOUND")
          || StringUtils.equals(wsHasMemberResults.getResultMetadata().getResultCode(), "IS_NOT_MEMBER")) {
        groupHasMemberResponse.setHasMember(false);
      } else if (StringUtils.equals(wsHasMemberResults.getResultMetadata().getResultCode(), "IS_MEMBER")) {
        groupHasMemberResponse.setHasMember(true);
      } else {
        throw new RuntimeException("Note expecting Grouper result code: " + wsHasMemberResults.getResultMetadata().getResultCode());
      }
    }
    return groupHasMemberResponse;

  }

}

Note, the service implementor only needs to worry about the current version of the FIFER API, the reference implementation will degrade gracefully to all previous versions of the API for all formats.