Thursday, May 6, 2010

Mule: Defining Properties at Runtime

I've had the desire for sometime to investigate telling Mule at run (or start-up) time what set of properties to use for a particular environment as in development, QA, or production, instead of deciding at build or deploy time what the target environment will be. So below I've come up with a rather simplistic service that uses a system property to define which set of properties to use at startup.

We will use the following Mule configuration element to define the environment properties file (explained in more detail here).

  <context:property-placeholder location="classpath:default.properties,classpath:${env}.properties" />

With the above line included in our Mule configuration, the default.properties file will be loaded first, followed by the environment-specific properties file. The latter will have the opportunity to override anything defined by the former.

The service will report the current season. A greeter bean configured in the Mule context will have two properties to be set via injection:

  • greetee - who the greeting will be addressed to
  • season - the current season (a true property of the environment)

The greeting will take the form of:

  "Hello ${greetee}, it is ${season}"

A default properties file will be used with the hope that we can eliminate having to repeat the property definitions
whose values would be common for every environment. We'll also investigate the ability to override this property in
case a particular environment would need to do so.

The default properties file will hold the 'greetee'.

    # default.properties
greetee = Friend
We'll have two season-defining properties files, one for spring and one for winter. The winter properties file will simply define the season:
    # winter.properties
season = winter
The spring properties file will define the season as well as override the greetee property set in default.properties:
    # spring.properties
greetee = Chap
season = spring

Since I'm using Maven 2 to build my service, we'll place the properties file in the src/main/resources directory. By convention, Maven will place all files found in this directory in the JAR file produced by the build. To make the files available to Mule on the classpath, we can place the JAR in hte lib/user directory of our Mule deployment so that they are available on the classpath as requested by the property-placeholder element.

The SeasonReporter class will have the two properties injected and will be used as a service component in our Mule model:

class SeasonReporter {
def greetee
def season
def reportSeason() { "Hello $greetee, it is $season" }
}

Our Mule configuration will have an inbound HTTP endpoint that will reply synchronously with the environment-specific greeting. The component that is invoked will be implemented by a SeasonReporter class that takes the two properties used to create the seasonal greeting.

We'll direct Mule to use the reportSeason method with no arguments (by default the HTTP transport will provide a payload of one argument, /season here):

<mule>
<context:property-placeholder location="classpath:default.properties,classpath:${env}.properties" />

<model name="seasonModel">
<service name="seasonService">
<inbound>
<http:inbound-endpoint address="http://localhost:8080/season"/>
</inbound>
<component>
<no-arguments-entry-point-resolver>
<include-entry-point method="reportSeason"/>
</no-arguments-entry-point-resolver>
<spring-object bean="seasonReporter"/>
</component>
</service>
</model>

<spring:bean name="seasonReporter" class="SeasonReporter" scope="prototype">
<spring:property name="greetee" value="${greetee}"/>
<spring:property name="season" value="${season}"/>
</spring:bean>
</mule>

To start Mule using the winter properties file, we can pass in the env property when starting Mule on the command line with:

  $ mule/bin/mule start -M-Denv=winter

After starting Mule and hitting URL http://localhost:8080/season in a browser, we are presented with:

  Hello Friend, it is winter

Now we can try starting Mule up for season spring. The greeting should now include the overridden greetee property of 'Chap':

  $ mule/bin/mule start -M-Denv=spring
Giving us:
  Hello Chap, it is spring

Thanks for reading!

Tuesday, April 20, 2010

Mule & CXF: Multiple Operations and Groovy Components

When I picture a web service in Mule, there is usually a component backing the endpoint. Here I have a case where I would want the service fielding the web service request to delegate its work to another service. To the client, the fact that Mule will pass the actual workload for a request to another service will be hidden.

In this example, we have a mock "warehouse", where a client can store and retrieve a box. A box is uniquely identified by an ID (a String). Each operation will result in the invocation of an additional, but separate, service to do the actual work.

Here's the interface defined for our warehouse:

package prystasj.warehouse;

import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService
public interface Warehouse {

@WebResult(name="receipt")
List<String> retrieve(@WebParam(name="boxId") String boxId);

@WebResult(name="box")
String store(@WebParam(name="boxId") String boxId);
}

The inteface defines both the operations. To determine which operation the client wishes to invoke, we can inspect the cxf_property of the Mule message created by the CXF Transport.

A request indicating the store operation will be routed to a storageService, while a retrieve operation will be routed to a retrievalService:

<model name="warehouse">
<service name="warehouseService">
<inbound>
<cxf:inbound-endpoint address="http://localhost:8080/warehouse"
serviceClass="prystasj.warehouse.Warehouse"/>
<inbound>
<outbound>
<filtering-router>
<vm:outbound-endpoint path="retrieval" synchronous="true"/>
<expression-filter evaluator="groovy"
expression="message.getProperty('cxf_operation').getLocalPart() == 'retrieve'"/>
<filtering-router>
<filtering-router>
<vm:outbound-endpoint path="storage" synchronous="true"/>
<expression-filter evaluator="groovy"
expression="message.getProperty('cxf_operation').getLocalPart() == 'store'"/>
<filtering-router>
<outbound>
<service>
</model>

To test the processing flow out, we can script a couple of components with Groovy. A couple of println's will help us pick out that the operations were invoked when viewing the Mule log. With a scripting component, we'll have access to a variable payload, which in each operation will contain the web parameters (a boxId in both cases) as defined in the Warehouse interface.

    <service name="retrievalService">
<inbound>
<vm:inbound-endpoint path="retrieval"/>
</inbound>
<script:component>
<script:script engine="groovy">
println "Operation: retrieve; Box ID: $payload"
new Box(payload)
<script:script>
<script:component>
<service>
<service name="storageService">
<inbound>
<vm:inbound-endpoint path="storage"/>
</inbound>
<script:component>
<script:script engine="groovy">
println "Operation: store; Box ID: $payload"
new Receipt(payload)
<script:script>
<script:component>
<service>

The store operation returns an instance of a Box and the retrieve operation returns a Receipt. As both services are invoked synchronously, the result of the component invocations will be returned to the calling Warehouse service, which will pass them through to the client.

Monday, March 29, 2010

XMLUnit: Simpler Asserts

Quite a while ago, I wrote about using XMLUnit in my unit tests to compare XML results.

The method used to create Diffs has been useful, but the project offers a simpler way to compare XML:

import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual

class AssertXmlEqualTest {

@Test void test_xml_is_similar() {
def expected = "<hi>bye</hi>"
def actual = "<hi>bye</hi>"
assertXMLEqual expected, actual
}
}
The test passes. If I change the value of actual to something different, say:

    def actual = "<hi>goodbye</hi>"

The test fails as the XML documents are no longer similar:

junit.framework.AssertionFailedError: org.custommonkey.xmlunit.Diff
[different] Expected text value 'bye' but was 'goodbye' -
comparing <hi ...>bye<hi> at /hi[1]/text()[1] to <hi ...>goodbye<hi> at /hi[1]/text()[1]
Since I have no reason in this simple case to be inspecting the Diff object created by XMLUnit as described in the previous posting, this method should serve as a nice substitute.

Sunday, March 7, 2010

Mule: Service Composition with Dependencies

Last time around, I did a little work with service composition in hopes to come up with an approach I could use in a real-world project. The approach I came up with last time appeared to fit the bill, until another requirement was added: The response from the first service would be needed to make the request to the second.

The new requirement definitely forced me to rethink my previous approach, as I could no longer simply send the intial request off to be routed to the two services that needed to be consumed, aggregating the responses for a request to the business logic.

For this example, I'll try to come up with a similar problem for a service to solve that follows a similar processing flow: The goal of this new service, given an order number, is to respond with a report about the customer containing their information and order history.

In order to accomplish this task, this new service will need to make use of two remote services:

  1. A customer service: Returns the information for a customer who placed a particular order.
  2. An order history service: Returns the order history for a particular customer.

The information contained in both service responses is used in the business logic contained within this composite service to produce a report, let's say to relate the ordering of certain products to different demographic profiles.

Now at this point, one might say: "Why can't the order history service return the customer information its provided in its request in its response?".

A good question, but some would argue that a service should not respond with data the caller already has or knows. My job here would be a lot of easier if the order history service could be changed to do just that, I could just pass the order history response to my business logic and not need to keep the customer service response around. On the other hand, the order history service should just be charged with returning the information it was requested to produce and not concern itself with how its response will ultimately be used, as long as that response is usable and correct. Not all clients might make use of the customer information returned in the order history response, and we should not request that type of overhead.

Ok, enough talking and more Mule-ing. For now I can start with the two things I know for certain, I will need to make requests to both the customer service and order history service, and they need to happen in order, so let's start there. For the sake of brevity, I'll omit any needed transformations to make the processing paths easier to follow.

<service name="orderService">
<inbound>
<jms:inbound-endpoint queue="order.queue" synchronous="true"/>
</inbound>
<outbound>
<chaining-router>
<vm:outbound-endpoint path="customer.queue" synchronous="true"/>
<vm:outbound-endpoint path="history.queue" synchronous="true"/>
</chaining-router>
</outbound>
</service>

The incoming request will be routed to a VM queue where a listening internal service will route it to the remote customer service:

<service name="customerService">
<inbound>
<vm:inbound-endpoint path="customer.queue"/>
</inbound>
<outbound>
<pass-through-router>
<jms:outbound-endpoint queue="customer.service.request.queue" synchronous="true"/>
</pass-through-router>
</outbound>
</service>

The response from the customer service will be provided to the order history service, which is defined similarly.

Now the next problem here is getting both responses to the same place so a request for my business logic can be created and the request carried out. From here, I'll add the internal service containing the business logic:

<service name="reportService">
<inbound>
<vm:inbound-endpoint path="responses.queue" synchronous="true"/>
<custom-inbound-router class="ResponseAggregator"/>
</inbound>
<component>
<spring-object bean="reportingComponent"/>
</component>
</service>

The inbound router is an extension of Mule's AbstractEventAggregator class that waits for a group of 2 responses (to avoid a context switch I'll omit the implementation details for now). It's job is to take both service responses and create a request for the reportingComponent. Now that we have that defined, how do we get the service responses to its VM queue?

Getting the order history response to the queue is easy, we just add another outbound endpoint to the chaining router:

<service name="orderService">
<inbound>
<jms:inbound-endpoint queue="order.queue" synchronous="true"/>
</inbound>
<outbound>
<chaining-router>
<vm:outbound-endpoint path="customer.queue" synchronous="true"/>
<vm:outbound-endpoint path="history.queue" synchronous="true"/>
<vm:outbound-endpoint path="responses.queue" synchronous="true"/>
</chaining-router>
</outbound>
</service>

Now we need to get the customer service response to the order history service and to the same response.queue. A "customer response routing service" might work here. This new service would be added as an endpoint in the chaining router between the routings to the customer and history services. This new internal service needs to route its request to the VM queue for the report service and return the request to the chaining router:

<service name="customerRoutingService">
<inbound>
<vm:inbound-endpoint path="customer.routing.queue" synchronous="false"/>
</inbound>
<outbound>
<multicasting-router>
<vm:outbound-endpoint path="responses.queue" synchronous="false"/>
<vm:outbound-endpoint path="async.queue" synchronous="false"/>
</multicasting-router>
</outbound>
<async-reply>
<vm:inbound-endpoint path="async.queue"/>
<single-async-reply-router>
<!-- we want to return the message that maps to the correlation ID found on the original request -->
<expression-message-info-mapping messageIdExpression="#[header:JMSCorrelationID]"
correlationIdExpression="#[header:JMSCorrelationID]"/>
</single-async-reply-router>
</async-reply>
</service>

Unlike the other internal services that respond synchronously, this service is asynchronous. To the caller, the chaining router, the request will have been handled synchronously, following the Async Request Response style.

The request received (the customer information), will be sent to the desired VM queue and to the async.queue where a reply router will return the customer information response it recevied on its inbound endpoint to the chaining router.

Now we place the routing to the customerRoutingService into the chain:

<service name="orderService">
<inbound>
<jms:inbound-endpoint queue="order.queue" synchronous="true"/>
</inbound>
<outbound>
<chaining-router>
<vm:outbound-endpoint path="customer.queue" synchronous="true"/>
<vm:outbound-endpoint path="customer.routing.queue" synchronous="true"/>
<vm:outbound-endpoint path="history.queue" synchronous="true"/>
<vm:outbound-endpoint path="responses.queue" synchronous="true"/>
</chaining-router>
</outbound>
</service>

Since the last endpoint in the chain will be the second message placed on responses.queue, the inbound router for the reportService will be invoked, in turn invoking the component. The response from the component will be returned to the chain, completing it. The chaining router will then return the response to the caller of the orderService.

To sum up the processing flow:

  1. A request is sent to the order service.
  2. The request is forwarded to the remote customer service.
  3. The customer service response is sent to the customer response routing service.
  4. The routing service sends the request to the responses queue (event #1) and to an async reply queue.
  5. A listener on the async reply queue returns the customer service response to the chaining router
  6. The customer service response is forwarded to the remote order history service.
  7. The order history response is sent to the responses queue (event #2).
  8. The aggreagation router listening to the responses queue, having both events, is invoked, creating the reporting request.
  9. The reporting component is invoked creating the service response.
  10. The response is returned to the chaining router, completing the chain.
  11. The response is returned to the caller.

This could one of several approaches to the stated problem, alternatives are welcome!

Saturday, February 13, 2010

Mule Example: Request Chaining and Response Aggregation

With a better understanding of the processing options available in Mule after reading Mule in Action, I found a place where some of the routing and aggregation examples would be of help in a current project. I came up with a prototype that models the messaging flow I was interested in before proceeding with work on my real-world project.

The processing model I wanted to prototype involved:

  1. A message is received on a inbound endpoint.
  2. The message is transformed and forwarded to each of two services.
  3. The responses of each service is collected and used to create a request for the business logic.
  4. The business logic is invoked and the response returned to the caller.

In my real-world project, I would of course need to consider the transformation of messages to meet the expected request formats by each service and the transports, JMS in my case, by which the services are invoked. In order to keep this example manageable, I decided to use simple components and the VM transport. This means the stand-ins for the services I want to invoke are defined in the same model as the service handling the business concerns.

In this example, I use the stock log-component element which provides an implementation of org.mule.api.component.simple.LogService. This component simply logs what is received on the inbound endpoint. If no component is declared, the inbound message is implicitly bridged to the outbound definition. This approach would fit just fine here for some of the services defined in the model, but I want to log the messages so we can more easily track the message flow.

Originally this example used echo-components, hence the use of "echo" instead of "log" throughout, but all log-components could be substitued with an echo-component to produce the same results with respect to the final response message.

Below is one possible implementation of the logical layout described above:

  1. A file is read of the file endpoint, directory /tmp/input.
  2. The contents of the file is logged and are sent to an outbound chaining router.
  3. The chaining router first routes to the echo-router service by a VM endpoint.
  4. The request is multi-cast to the echo-one and echo-two services.
  5. Each response is sent to the echo-response endpoint where there are aggregated and returned to the chaining router.
  6. The chaining router forwards the aggreagated response to the main-logic endpoint where the business logic is invoked.

At the start of processing a message is received by the echo-start service:

<service name="echo-start">
<inbound>
<file:inbound-endpoint path="/tmp/input"/>
</inbound>
<log-component/>
<outbound>
<chaining-router>
<vm:outbound-endpoint path="echo-requests"/>
<vm:outbound-endpoint path="main-request"/>
</chaining-router>
</outbound>
</service>

The file contents are first sent to the echo-requests endpoint. The goal here is to have the service listening to the endpoint invoke the other two response and send the aggreage response back, where the chaining router would forward it to the main-request endpoint where the business logic of the real-world service would accept it.

The echo-router service receives the request on the echo-requests endpoint, where the request is sent to the endpoint for each service we want to invoke:

<outbound>
<multicasting-router enableCorrelation="ALWAYS">
<vm:outbound-endpoint address="vm://echo-one-request"/>
<vm:outbound-endpoint address="vm://echo-two-request"/>
<reply-to address="vm://echo-responses"/>
</multicasting-router>
</outbound>

As the target services are invoked asynchronously, the enableCorrelation attribute is needed so any response can be related to the request. Additionally, we need to tell the router where to send the responses from each service with the reply-to element.

The responses for the "echo" services (the implemenations of which will be provided later) as sent to the echo-responses endpoint where a custom aggregator is listening:

<async-reply>
<vm:inbound-endpoint path="echo-responses"/>
<custom-async-reply-router class="prystasj.mule.EchoResponseAggregator"/>
</async-reply>

The response from the aggregator is given back to the chaining-router and forwarded to the main-request endpoint where it is simply logged. Below is the entire model. Since it's rather lengthy, I promise to descibe the custom aggregator and an example invocation soon after.

<model name="echoAggregationModel">
<service name="echo-start">
<inbound>
<file:inbound-endpoint path="/tmp/input" />
</inbound>
<log-component />
<outbound>
<chaining-router>
<vm:outbound-endpoint path="echo-requests" />
<vm:outbound-endpoint path="main-request" />
</chaining-router>
</outbound>
</service>
<service name="echo-router">
<inbound>
<vm:inbound-endpoint path="echo-requests" />
</inbound>
<log-component />
<outbound>
<multicasting-router enableCorrelation="ALWAYS">
<vm:outbound-endpoint address="vm://echo-one-request" />
<vm:outbound-endpoint address="vm://echo-two-request" />
<reply-to address="vm://echo-responses" />
</multicasting-router>
</outbound>
<async-reply>
<vm:inbound-endpoint path="echo-responses" />
<custom-async-reply-router class="prystasj.mule.EchoResponseAggregator" />
</async-reply>
</service>
<service name="main-logic">
<inbound>
<vm:inbound-endpoint path="main-request" />
</inbound>
<log-component />
</service>
<service name="echo-one">
<inbound>
<vm:inbound-endpoint address="vm://echo-one-request" />
</inbound>
<log-component />
</service>
<service name="echo-two">
<inbound>
<vm:inbound-endpoint address="vm://echo-two-request" />
</inbound>
<log-component />
</service>
</model>

The custom aggregator simply concatenates the responses it sees. If the original request file contained "HELLO", the response from the aggregator would be "HELLOHELLO" since the responses from the service that are invoked are simply echoes of what they've received.

The custom aggregator extends the org.mule.routing.response.ResponseCorrelationAggregator class since correlation was enabled to map the responses from the echo services to the originating request.

Here is the code for the aggregator, for brevity, I've ommitted any guards and checks:

public class EchoResponseAggregator extends ResponseCorrelationAggregator {

@Override
protected EventCorrelatorCallback getCorrelatorCallback() {
return new CollectionCorrelatorCallback() {
public MuleMessage aggregateEvents(EventGroup events) throws AggregationException {
try {
return aggregateResponsesFromEvents(events);
} catch(Exception e) {
throw new AggregationException(events, null ,e);
}
}
};
}

public static MuleMessage aggregateResponsesFromEvents(EventGroup events) throws Exception {
String aggregateResponse = "";
MuleEvent event = null;

for (Iterator iterator = events.iterator(); iterator.hasNext();) {
event = (MuleEvent)iterator.next();
String payload = event.getMessage().getPayloadAsString();
aggregateResponse += payload;
}

return new DefaultMuleMessage(aggregateResponse, event.getMessage());
}
}

Hope this example helps anyone looking to do something similar. Any suggestions on how to simplify things would also be appreciated. Thanks!.

Sunday, February 7, 2010

Maven: Testing XSLT Transforms

Looking for a quick way to test a XSLT Transformations, I decided to check out the XML Maven Plugin hosted at the Codehaus Mojo Project.

For illustrative purposes, I want to turn a XML document of people into HTML:

<people>
<person>
<firstName>Ronnie</firstName>
<lastName>Gardocki</lastName>
</person>
<person>
<firstName>Shane</firstName>
<lastName>Vendrel</lastName>
</person>
</people>

With the following simple stylesheet:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
<html>
<head>
<title>People in HTML</title>
<body>
<h1>People in HTML</h1>

<xsl:apply-templates/>

</body>
</head>
</html>
</xsl:template>

<xsl:template match="person">
<table>
<th>Person</th>
<td><xsl:value-of select="firstName"/></td>
<td><xsl:value-of select="lastName"/></td>
</table>
</xsl:template>

</xsl:stylesheet>

The xml:transform plugin goal takes, among others, a dir parameter to configure where the files are you want to transform, and a stylesheet parameter describing the XSLT file to use to transform the documents found in the directory. Here I'm also overriding the default output directory for the transformed documents, target/generated-resources/xml/xslt:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
<version>1.0-beta-3</version>
<executions>
<execution>
<goals>
<goal>transform</goal>
</goals>
</execution>
</executions>
<configuration>
<transformationSets>
<transformationSet>
<dir>src/main/xml</dir>
<stylesheet>src/main/xslt/${stylesheet}</stylesheet>
<outputDir>target</outputDir>
</transformationSet>
</transformationSets>
</configuration>
</plugin>

To run an example with the configuration above, I pass in the name of the stylesheet to use which is located in src/main/xslt. I would of liked to have been able to pass in a specific document to transform, but was not able to find a way to configure the plugin to do so.

$ mvn xml:transform -Dstylesheet=people-to-html.xsl
The relevant output provided by the plugin:
[INFO] [xml:transform]
[INFO] Transforming file: /home/prystasj/workspace/prystasj/xslt-test/src/main/xml/people.xml
[INFO] Transformed 1 file(s).

The transform produces this simple HTML:

<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>People in HTML</title>
<body>
<h1>People in HTML</h1>

<table>
<th>Person</th><td>Ronnie</td><td>Gardocki</td>
</table>

<table>
<th>Person</th><td>Shane</td><td>Vendrel</td>
</table>

</body>
</head>
</html>

If we want to try out a new transform, we can drop a new stylesheet and example source documents in the appropriate directories, hopefully creating some quick turnaround time for testing some transforms.

Groovy: Grouping by Multiple Properties

In a recent Groovy program, I had a need to group a list of objects by multiple properties. To give an example, take the following class:

class Person {
def firstName
def lastName
def city
def hobby

String toString() {
"Person[city=$city, hobby=$hobby, firstName=$firstName, lastName=$lastName]"
}
}

I wanted to group things by city and hobby so people can find others nearby who have similar interests. Grouping by one property of the class is easy enough using the groupBy method. Here I'd receive a map, keyed by each found hobby:

def people = [
new Person(city:"Queens", hobby:"Scrabble", firstName:"Frank", lastName:"Costanza"),
new Person(city:"Queens", hobby:"Scrabble", firstName:"Estelle", lastName:"Costanza"),
new Person(city:"Brooklyn", hobby:"Scrabble", firstName:"David", lastName:"Puddy"),
new Person(city:"Brooklyn", hobby:"Scrabble", firstName:"Lloyd", lastName:"Braun"),
new Person(city:"Brooklyn", hobby:"Architecture", firstName:"Art", lastName:"Vandelay"),
]

def map = people.groupBy { it.hobby }
println "Number of groups: ${groups.size()}"
println groups.each { hobby, group -> println "$hobby: ${group.size()}" }

This gives me two groups, one for "Scrabble", and one for "Architecture":

  Number of groups: 2
Scrabble: 4
Architecture: 1

A method to add the city property to the grouping did not seem so apparent to me. I did come up with one method that feels rather hacky, so I'll share it here and see if anybody can helpfully suggest a better approach.

Here I'll make a unique key, by combining the city and hobby properties, separated by a hyphen. I have no need for the key used for grouping here after the grouping is done, so I just grab the values of the created map, which should be a list of Person lists:

groups = people.groupBy { "${it.city}-${it.hobby}" }.values()
println "Number of groups: ${groups.size()}"

groups.each { group ->
println "Group:"
group.each { person ->
println "\t$person"
}
}

Which gives me the 3 groups I was looking for:

  Number of groups: 3
Group:
Person[city=Queens, hobby=Scrabble, firstName=Frank, lastName=Costanza]
Person[city=Queens, hobby=Scrabble, firstName=Estelle, lastName=Costanza]
Group:
Person[city=Brooklyn, hobby=Scrabble, firstName=David, lastName=Puddy]
Person[city=Brooklyn, hobby=Scrabble, firstName=Lloyd, lastName=Braun]
Group:
Person[city=Brooklyn, hobby=Architecture, firstName=Art, lastName=Vandelay]

Here I could also consider popluating a list of Group objects with unique Group object with city and hobby properties that could also hold a Person list.