summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java90
-rw-r--r--src/main/java/com/p4square/ccbapi/CCBAPI.java42
-rw-r--r--src/main/java/com/p4square/ccbapi/CCBAPIClient.java171
-rw-r--r--src/main/java/com/p4square/ccbapi/CCBXmlBinder.java78
-rw-r--r--src/main/java/com/p4square/ccbapi/HTTPInterface.java34
-rw-r--r--src/main/java/com/p4square/ccbapi/LocalDateTimeXmlAdapter.java26
-rw-r--r--src/main/java/com/p4square/ccbapi/LocalDateXmlAdapter.java21
-rw-r--r--src/main/java/com/p4square/ccbapi/exception/CCBErrorResponseException.java24
-rw-r--r--src/main/java/com/p4square/ccbapi/exception/CCBException.java16
-rw-r--r--src/main/java/com/p4square/ccbapi/exception/CCBParseException.java14
-rw-r--r--src/main/java/com/p4square/ccbapi/exception/CCBRetryableErrorException.java12
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Address.java125
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CCBAPIResponse.java35
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CCBErrorResponse.java48
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Country.java48
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CustomDateFieldValue.java24
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CustomField.java74
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CustomFieldCollection.java137
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CustomPulldownFieldValue.java23
-rw-r--r--src/main/java/com/p4square/ccbapi/model/CustomTextFieldValue.java23
-rw-r--r--src/main/java/com/p4square/ccbapi/model/FamilyMemberReference.java34
-rw-r--r--src/main/java/com/p4square/ccbapi/model/FamilyPosition.java26
-rw-r--r--src/main/java/com/p4square/ccbapi/model/FamilyReference.java34
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Gender.java24
-rw-r--r--src/main/java/com/p4square/ccbapi/model/GetCustomFieldLabelsResponse.java37
-rw-r--r--src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesRequest.java159
-rw-r--r--src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java27
-rw-r--r--src/main/java/com/p4square/ccbapi/model/IndividualProfile.java449
-rw-r--r--src/main/java/com/p4square/ccbapi/model/IndividualReference.java34
-rw-r--r--src/main/java/com/p4square/ccbapi/model/MaritalStatus.java28
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Phone.java41
-rw-r--r--src/main/java/com/p4square/ccbapi/model/PulldownSelection.java34
-rw-r--r--src/main/java/com/p4square/ccbapi/model/package-info.java16
33 files changed, 2008 insertions, 0 deletions
diff --git a/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java b/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java
new file mode 100644
index 0000000..0833c67
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java
@@ -0,0 +1,90 @@
+package com.p4square.ccbapi;
+
+import com.p4square.ccbapi.exception.CCBException;
+import com.p4square.ccbapi.exception.CCBRetryableErrorException;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ApacheHttpClientImpl is an implementation of HTTPInterface which uses the Apache HTTP Client library.
+ */
+public class ApacheHttpClientImpl implements HTTPInterface {
+
+ private final DefaultHttpClient httpClient;
+
+ public ApacheHttpClientImpl(final URI apiBaseUri, final String username, final String password) {
+ // Create the HTTP client.
+ this.httpClient = new DefaultHttpClient();
+
+ // Prepare the CredentialsProvider for the HTTP Client.
+ int port = apiBaseUri.getPort();
+ if (port == -1) {
+ if ("http".equalsIgnoreCase(apiBaseUri.getScheme())) {
+ port = 80;
+ } else if ("https".equalsIgnoreCase(apiBaseUri.getScheme())) {
+ port = 443;
+ } else {
+ throw new IllegalArgumentException("Cannot determine port for unknown scheme.");
+ }
+ }
+ this.httpClient.getCredentialsProvider().setCredentials(new AuthScope(apiBaseUri.getHost(), port),
+ new UsernamePasswordCredentials(username, password));
+ }
+
+ @Override
+ public void close() throws IOException {
+ httpClient.getConnectionManager().shutdown();
+ }
+
+ @Override
+ public InputStream sendPostRequest(URI uri, Map<String, String> form) throws IOException {
+ // Build the request.
+ final HttpPost httpPost = new HttpPost(uri);
+ final List<NameValuePair> formParameters = new ArrayList<>();
+ for (Map.Entry<String, String> entry : form.entrySet()) {
+ formParameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
+ }
+ httpPost.setEntity(new UrlEncodedFormEntity(formParameters));
+
+ // Make the request.
+ final HttpResponse response = httpClient.execute(httpPost);
+
+ // Process the response.
+ final int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != 200) {
+ // Consume the entity and close the connection.
+ EntityUtils.consume(response.getEntity());
+
+ // Determine the type of failure and throw an exception.
+ if (statusCode >= 400 && statusCode < 500) {
+ throw new CCBException("Unexpected non-retryable error: " + response.getStatusLine().toString());
+ } else if (statusCode >= 500 && statusCode < 600) {
+ throw new CCBRetryableErrorException("Retryable error: " + response.getStatusLine().toString());
+ } else {
+ throw new CCBException("Unexpected status code: " + response.getStatusLine().toString());
+ }
+ }
+
+ final HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ return entity.getContent();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/CCBAPI.java b/src/main/java/com/p4square/ccbapi/CCBAPI.java
new file mode 100644
index 0000000..e54e20b
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/CCBAPI.java
@@ -0,0 +1,42 @@
+package com.p4square.ccbapi;
+
+import com.p4square.ccbapi.model.*;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * CCBAPI is a Java interface for using the Church Community Builder API.
+ */
+public interface CCBAPI extends Closeable {
+ /**
+ * Retrieve the set of custom (user-defined) fields and the associated labels.
+ *
+ * @return A GetCustomFieldLabelsResponse containing the fields.
+ * @throws IOException on failure.
+ */
+ GetCustomFieldLabelsResponse getCustomFieldLabels() throws IOException;
+
+ /**
+ * Retrieve one or more IndividualProfiles.
+ *
+ * If any of the following properties are set on the request,
+ * this method will return the matching individual, if one exists.
+ *
+ * <ul>
+ * <li>Individual ID</li>
+ * <li>Login and Password</li>
+ * <li>MICR</li>
+ * </ul>
+ *
+ * If more than one property is included only the first, in the order listed above, will be used.
+ * If none of the options are included, all individuals will be returned.
+ *
+ * The appropriate CCB API will be selected based on the options used.
+ *
+ * @param request A GetIndividualProfilesRequest.
+ * @return A GetIndividualProfilesResponse object on success, including when no individuals match.
+ * @throws IOException on failure.
+ */
+ GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) throws IOException;
+}
diff --git a/src/main/java/com/p4square/ccbapi/CCBAPIClient.java b/src/main/java/com/p4square/ccbapi/CCBAPIClient.java
new file mode 100644
index 0000000..782f305
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/CCBAPIClient.java
@@ -0,0 +1,171 @@
+package com.p4square.ccbapi;
+
+import com.p4square.ccbapi.exception.CCBErrorResponseException;
+import com.p4square.ccbapi.model.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * CCBAPIClient is an implementation of CCBAPI using the Apache HttpClient.
+ *
+ * This implementation is built against the API documentations found here:
+ * https://designccb.s3.amazonaws.com/helpdesk/files/official_docs/api.html
+ *
+ * This client is thread-safe.
+ */
+public class CCBAPIClient implements CCBAPI {
+
+ private static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
+
+ private final URI apiBaseUri;
+ private final HTTPInterface httpClient;
+ private final CCBXmlBinder xmlBinder;
+
+ /**
+ * Create a new CCB API Client.
+ *
+ * @param church The church identifier used with CCB.
+ * @param username The API username.
+ * @param password The API password.
+ * @throws URISyntaxException If the church parameter contains unsafe URI characters.
+ */
+ public CCBAPIClient(final String church, final String username, final String password) throws URISyntaxException {
+ this(new URI("https://" + church + ".ccbchurch.com/api.php"), username, password);
+ }
+
+ /**
+ * Create a new CCB API Client.
+ *
+ * @param apiUri The base URI to use when contacting CCB.
+ * @param username The API username.
+ * @param password The API password.
+ */
+ public CCBAPIClient(final URI apiUri, final String username, final String password) {
+ this(apiUri, new ApacheHttpClientImpl(apiUri, username, password));
+ }
+
+ /**
+ * A private constructor which allows for dependency injection.
+ *
+ * @param apiUri The base URI to use when contacting CCB.
+ * @param httpClient The HTTP client used to send requests.
+ */
+ protected CCBAPIClient(final URI apiUri, final HTTPInterface httpClient) {
+ this.apiBaseUri = apiUri;
+ this.httpClient = httpClient;
+ this.xmlBinder = new CCBXmlBinder();
+ }
+
+ @Override
+ public void close() throws IOException {
+ httpClient.close();
+ }
+
+ @Override
+ public GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) throws IOException {
+ // Prepare the request.
+ String serviceName;
+ final Map<String, String> params = new HashMap<>();
+ if (request.getId() != 0) {
+ // Use individual_profile_from_id (individual_id)
+ serviceName = "individual_profile_from_id";
+ params.put("individual_id", String.valueOf(request.getId()));
+
+ } else if (request.getLogin() != null && request.getPassword() != null) {
+ // Use individual_profile_from_login_password (login, password)
+ serviceName = "individual_profile_from_login_password";
+ params.put("login", request.getLogin());
+ params.put("password", request.getPassword());
+
+ } else if (request.getRoutingNumber() != null && request.getAccountNumber() != null) {
+ // Use individual_profile_from_micr (account_number, routing_number)
+ serviceName = "individual_profile_from_micr";
+ params.put("routing_number", request.getRoutingNumber());
+ params.put("account_number", request.getAccountNumber());
+
+ } else {
+ // Use individual_profiles
+ serviceName = "individual_profiles";
+ if (request.getModifiedSince() != null) {
+ params.put("modified_since", request.getModifiedSince().toString());
+ }
+ if (request.getIncludeInactive() != null) {
+ params.put("include_inactive", request.getIncludeInactive() ? "true" : "false");
+ }
+ if (request.getPage() != 0) {
+ params.put("page", String.valueOf(request.getPage()));
+ }
+ if (request.getPerPage() != 0) {
+ params.put("per_page", String.valueOf(request.getPerPage()));
+ }
+ }
+
+ // Send the request and parse the response.
+ return makeRequest(serviceName, params, EMPTY_MAP, GetIndividualProfilesResponse.class);
+ }
+
+ @Override
+ public GetCustomFieldLabelsResponse getCustomFieldLabels() throws IOException {
+ return makeRequest("custom_field_labels", EMPTY_MAP, EMPTY_MAP, GetCustomFieldLabelsResponse.class);
+ }
+
+ /**
+ * Build the URI for a particular service call.
+ *
+ * @param service The CCB API service to call (i.e. the srv query parameter).
+ * @param parameters A map of query parameters to include on the URI.
+ * @return The apiBaseUri with the additional query parameters appended.
+ */
+ private URI makeURI(final String service, final Map<String, String> parameters) {
+ try {
+ StringBuilder queryStringBuilder = new StringBuilder();
+ if (apiBaseUri.getQuery() != null) {
+ queryStringBuilder.append(apiBaseUri.getQuery()).append("&");
+ }
+ queryStringBuilder.append("srv=").append(service);
+ for (Map.Entry<String, String> entry: parameters.entrySet()) {
+ queryStringBuilder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ return new URI(apiBaseUri.getScheme(), apiBaseUri.getAuthority(), apiBaseUri.getPath(),
+ queryStringBuilder.toString(), apiBaseUri.getFragment());
+ } catch (URISyntaxException e) {
+ // This shouldn't happen, but needs to be caught regardless.
+ throw new AssertionError("Could not construct API URI", e);
+ }
+ }
+
+ /**
+ * Send a request to CCB.
+ *
+ * @param api The CCB service name.
+ * @param params The URL query params.
+ * @param form The form body parameters.
+ * @param clazz The response class.
+ * @param <T> The type of response.
+ * @return The response.
+ * @throws IOException if an error occurs.
+ */
+ private <T extends CCBAPIResponse> T makeRequest(final String api, final Map<String, String> params,
+ final Map<String, String> form, final Class<T> clazz)
+ throws IOException {
+
+ final InputStream entity = httpClient.sendPostRequest(makeURI(api, params), form);
+ try {
+ T response = xmlBinder.bindResponseXML(entity, clazz);
+ if (response.getErrors() != null && response.getErrors().size() > 0) {
+ throw new CCBErrorResponseException(response.getErrors());
+ }
+ return response;
+ } finally {
+ if (entity != null) {
+ entity.close();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/CCBXmlBinder.java b/src/main/java/com/p4square/ccbapi/CCBXmlBinder.java
new file mode 100644
index 0000000..f347643
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/CCBXmlBinder.java
@@ -0,0 +1,78 @@
+package com.p4square.ccbapi;
+
+import com.p4square.ccbapi.exception.CCBParseException;
+import com.p4square.ccbapi.model.CCBAPIResponse;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.InputStream;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * CCBXmlBinder is used to bind XML responses to CCBAPIResponse implementations.
+ *
+ * This class is thread-safe.
+ */
+public class CCBXmlBinder {
+ private final XMLInputFactory xmlInputFactory;
+ private final ConcurrentHashMap<Class<? extends CCBAPIResponse>, JAXBContext> jaxbContextCache;
+
+ public CCBXmlBinder() {
+ xmlInputFactory = XMLInputFactory.newFactory();
+ jaxbContextCache = new ConcurrentHashMap<>();
+ }
+
+ public <T extends CCBAPIResponse> T bindResponseXML(InputStream response, Class<T> responseClass)
+ throws CCBParseException {
+ try {
+ final XMLStreamReader xmlReader = xmlInputFactory.createXMLStreamReader(response);
+ final JAXBContext jaxbContext = getJAXBContext(responseClass);
+ final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+
+ // Skip ahead to the response entity.
+ while (xmlReader.hasNext()) {
+ xmlReader.next();
+ if (xmlReader.isStartElement() && "response".equalsIgnoreCase(xmlReader.getLocalName())) {
+ // Parse and return the response.
+ // If the response cannot be parsed a JAXBException will be thrown.
+ return (T) unmarshaller.unmarshal(xmlReader);
+ }
+ }
+
+ // If we reach this point then the response did not contain a response element.
+ throw new CCBParseException("Response did not contain a response element.");
+
+ } catch (XMLStreamException | JAXBException e) {
+ throw new CCBParseException("Failed to parse response.", e);
+ }
+ }
+
+ /**
+ * Find or create the JAXBContext for a CCBAPIResponse implementation.
+ *
+ * @param responseClass The response implementation class.
+ * @return a JAXBContext which can be used to unmarshell the responseClass.
+ */
+ private JAXBContext getJAXBContext(Class<? extends CCBAPIResponse> responseClass) {
+ if (!jaxbContextCache.containsKey(responseClass)) {
+ synchronized (jaxbContextCache) {
+ // Check again to be sure.
+ if (!jaxbContextCache.containsKey(responseClass)) {
+ try {
+ final JAXBContext jaxbContext = JAXBContext.newInstance(responseClass);
+ jaxbContextCache.put(responseClass, jaxbContext);
+ } catch (JAXBException e) {
+ throw new AssertionError("Could not construct JAXBContext for " + responseClass.getName(), e);
+ }
+ }
+ }
+ }
+ return jaxbContextCache.get(responseClass);
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/HTTPInterface.java b/src/main/java/com/p4square/ccbapi/HTTPInterface.java
new file mode 100644
index 0000000..11d87ec
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/HTTPInterface.java
@@ -0,0 +1,34 @@
+package com.p4square.ccbapi;
+
+import com.p4square.ccbapi.exception.CCBException;
+import com.p4square.ccbapi.exception.CCBRetryableErrorException;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * HTTPInterface exists to separate the HTTP client implementation from the CCB client.
+ *
+ * The concern here is that it may not be possible or desirable to take a dependency on the
+ * Apache HTTP Client library. If that case arises the CCBAPIClient and the HTTPInterface
+ * implementation can be split into separate packages. For simplicity's sake I'm not doing
+ * that now. But if it needs to be done at a later time the code will already be isolated.
+ */
+public interface HTTPInterface extends Closeable {
+ /**
+ * Send an HTTP POST request to the given URI.
+ *
+ * The form data for the request is specified in the form Map.
+ *
+ * @param uri The URI to request.
+ * @param form Map of key/value pairs to send as form data.
+ * @return The response received.
+ * @throws com.p4square.ccbapi.exception.CCBRetryableErrorException
+ * @throws CCBRetryableErrorException if a retryable error occurs.
+ * @throws IOException If a non-retryable error occurs.
+ */
+ InputStream sendPostRequest(URI uri, Map<String, String> form) throws IOException;
+}
diff --git a/src/main/java/com/p4square/ccbapi/LocalDateTimeXmlAdapter.java b/src/main/java/com/p4square/ccbapi/LocalDateTimeXmlAdapter.java
new file mode 100644
index 0000000..f6ee565
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/LocalDateTimeXmlAdapter.java
@@ -0,0 +1,26 @@
+package com.p4square.ccbapi;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * XmlAdapter implementation to convert CCB dates to LocalDateTime.
+ *
+ * Note that the datetime format used by CCB is not ISO-8601 formatted.
+ */
+public class LocalDateTimeXmlAdapter extends XmlAdapter<String, LocalDateTime> {
+
+ private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ public LocalDateTime unmarshal(final String value) throws Exception {
+ return LocalDateTime.parse(value, FORMAT);
+ }
+
+ @Override
+ public String marshal(final LocalDateTime value) throws Exception {
+ return value.format(FORMAT);
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/com/p4square/ccbapi/LocalDateXmlAdapter.java b/src/main/java/com/p4square/ccbapi/LocalDateXmlAdapter.java
new file mode 100644
index 0000000..861f18f
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/LocalDateXmlAdapter.java
@@ -0,0 +1,21 @@
+package com.p4square.ccbapi;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import java.time.LocalDate;
+
+/**
+ * XmlAdapter implementation for LocalDate.
+ */
+public class LocalDateXmlAdapter extends XmlAdapter<String, LocalDate> {
+
+ @Override
+ public LocalDate unmarshal(final String value) throws Exception {
+ return LocalDate.parse(value);
+ }
+
+ @Override
+ public String marshal(final LocalDate value) throws Exception {
+ return value.toString();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/com/p4square/ccbapi/exception/CCBErrorResponseException.java b/src/main/java/com/p4square/ccbapi/exception/CCBErrorResponseException.java
new file mode 100644
index 0000000..dd13f75
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/exception/CCBErrorResponseException.java
@@ -0,0 +1,24 @@
+package com.p4square.ccbapi.exception;
+
+import com.p4square.ccbapi.model.CCBErrorResponse;
+
+import java.util.List;
+
+/**
+ * CCBErrorResponseException is thrown when the CCB API returns one or more error responses.
+ */
+public class CCBErrorResponseException extends CCBException {
+ private final List<CCBErrorResponse> errors;
+
+ public CCBErrorResponseException(List<CCBErrorResponse> errors) {
+ super("CCB API service responded with errors: " + errors);
+ this.errors = errors;
+ }
+
+ /**
+ * @return The error response returned by the service.
+ */
+ public List<CCBErrorResponse> getErrors() {
+ return errors;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/exception/CCBException.java b/src/main/java/com/p4square/ccbapi/exception/CCBException.java
new file mode 100644
index 0000000..6d74ea4
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/exception/CCBException.java
@@ -0,0 +1,16 @@
+package com.p4square.ccbapi.exception;
+
+import java.io.IOException;
+
+/**
+ * Common exception class for all CCB API library exceptions.
+ */
+public class CCBException extends IOException {
+ public CCBException(String message) {
+ super(message);
+ }
+
+ public CCBException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/exception/CCBParseException.java b/src/main/java/com/p4square/ccbapi/exception/CCBParseException.java
new file mode 100644
index 0000000..aee0e34
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/exception/CCBParseException.java
@@ -0,0 +1,14 @@
+package com.p4square.ccbapi.exception;
+
+/**
+ * CCBParseException is thrown when a response from CCB cannot be parsed.
+ */
+public class CCBParseException extends CCBException {
+ public CCBParseException(String message) {
+ super(message);
+ }
+
+ public CCBParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/exception/CCBRetryableErrorException.java b/src/main/java/com/p4square/ccbapi/exception/CCBRetryableErrorException.java
new file mode 100644
index 0000000..c222662
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/exception/CCBRetryableErrorException.java
@@ -0,0 +1,12 @@
+package com.p4square.ccbapi.exception;
+
+/**
+ * CCBRetryableErrorException is thrown when a retryable error is received.
+ *
+ * The caller may retry the request with an appropriate back-off.
+ */
+public class CCBRetryableErrorException extends CCBException {
+ public CCBRetryableErrorException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/Address.java b/src/main/java/com/p4square/ccbapi/model/Address.java
new file mode 100644
index 0000000..9bbd6e3
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/Address.java
@@ -0,0 +1,125 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+
+/**
+ * Representation of a United States postal address.
+ */
+@XmlRootElement(name="address")
+@XmlAccessorType(XmlAccessType.NONE)
+public class Address {
+ @XmlType(namespace="Address")
+ public enum Type {
+ @XmlEnumValue("mailing") MAILING,
+ @XmlEnumValue("home") HOME,
+ @XmlEnumValue("work") WORK,
+ @XmlEnumValue("other") OTHER;
+ }
+
+ @XmlAttribute(name="type")
+ private Type type;
+
+ @XmlElement(name="street_address")
+ private String streetAddress;
+
+ @XmlElement(name="city")
+ private String city;
+
+ @XmlElement(name="state")
+ private String state;
+
+ @XmlElement(name="zip")
+ private String zip;
+
+ @XmlElement(name="country")
+ private Country country;
+
+ @XmlElement(name="line_1")
+ private String line_1;
+
+ @XmlElement(name="line_2")
+ private String line_2;
+
+ @XmlElement(name="latitude")
+ private String latitude;
+
+ @XmlElement(name="longitude")
+ private String longitude;
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public String getStreetAddress() {
+ return streetAddress;
+ }
+
+ public void setStreetAddress(String streetAddress) {
+ this.streetAddress = streetAddress;
+ updateAddressLines();
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ updateAddressLines();
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ if (state.length() < 2 || state.length() > 3) {
+ throw new IllegalArgumentException("Invalid state code.");
+ }
+ this.state = state.toUpperCase();
+ updateAddressLines();
+ }
+
+ public String getZip() {
+ return zip;
+ }
+
+ public void setZip(String zip) {
+ this.zip = zip;
+ updateAddressLines();
+ }
+
+ public Country getCountry() {
+ return country;
+ }
+
+ public void setCountry(Country country) {
+ this.country = country;
+ updateAddressLines();
+ }
+
+ public String getLine_1() {
+ return line_1;
+ }
+
+ public String getLine_2() {
+ return line_2;
+ }
+
+ public String getLatitude() {
+ return latitude;
+ }
+
+ public String getLongitude() {
+ return longitude;
+ }
+
+ private void updateAddressLines() {
+ this.line_1 = streetAddress;
+ this.line_2 = String.format("%s, %s %s", city, state, zip);
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CCBAPIResponse.java b/src/main/java/com/p4square/ccbapi/model/CCBAPIResponse.java
new file mode 100644
index 0000000..d54c8a9
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CCBAPIResponse.java
@@ -0,0 +1,35 @@
+package com.p4square.ccbapi.model;
+
+import com.p4square.ccbapi.model.CCBErrorResponse;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import java.util.List;
+
+/**
+ * Base class for all responses from the CCB API.
+ */
+public abstract class CCBAPIResponse {
+
+ @XmlElementWrapper(name="errors", nillable=true)
+ @XmlElement(name="error")
+ private List<CCBErrorResponse> errorResponses;
+
+ /**
+ * Return the error message if present.
+ *
+ * @return A CCBErrorResponse if an error occurred. Null if the request was successful.
+ */
+ public List<CCBErrorResponse> getErrors() {
+ return errorResponses;
+ }
+
+ /**
+ * Set the error response.
+ *
+ * @param error The CCBErrorResponse to set.
+ */
+ public void setErrors(final List<CCBErrorResponse> errors) {
+ this.errorResponses = errors;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CCBErrorResponse.java b/src/main/java/com/p4square/ccbapi/model/CCBErrorResponse.java
new file mode 100644
index 0000000..7fb1948
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CCBErrorResponse.java
@@ -0,0 +1,48 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+
+/**
+ * Representation of an error response returned by CCB.
+ */
+@XmlRootElement(name="error")
+@XmlAccessorType(XmlAccessType.NONE)
+public class CCBErrorResponse {
+ @XmlAttribute
+ private int number;
+
+ @XmlAttribute
+ private String type;
+
+ @XmlValue
+ private String description;
+
+ public int getNumber() {
+ return number;
+ }
+
+ public void setNumber(int number) {
+ this.number = number;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%03d %s Error: %s", getNumber(), getType(), getDescription());
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/Country.java b/src/main/java/com/p4square/ccbapi/model/Country.java
new file mode 100644
index 0000000..e6936a5
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/Country.java
@@ -0,0 +1,48 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+
+/**
+ * Country code and name pair.
+ */
+@XmlRootElement(name="country")
+@XmlAccessorType(XmlAccessType.NONE)
+public class Country {
+
+ @XmlAttribute(name="code")
+ private String code;
+
+ @XmlValue
+ private String name;
+
+ /**
+ * @return The two letter country code.
+ */
+ public String getCountryCode() {
+ return code;
+ }
+
+ /**
+ * Set the two letter country code.
+ *
+ * This class does not attempt to resolve the country name from the country code.
+ * When the country code is set the name will become null.
+
+ * @param code A two letter countey code.
+ * @throws IllegalArgumentException if the country code is not valid.
+ */
+ public void setCountryCode(final String code) {
+ if (code.length() != 2) {
+ throw new IllegalArgumentException("Argument must be a two letter country code.");
+ }
+ this.code = code.toUpperCase();
+ this.name = null;
+ }
+
+ /**
+ * @return The country name or null if the country name is unknown.
+ */
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CustomDateFieldValue.java b/src/main/java/com/p4square/ccbapi/model/CustomDateFieldValue.java
new file mode 100644
index 0000000..01436b7
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CustomDateFieldValue.java
@@ -0,0 +1,24 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import java.time.LocalDate;
+
+/**
+ * A user-defined date field and the associated value.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class CustomDateFieldValue extends CustomField {
+
+ @XmlElement(name="date")
+ private LocalDate date;
+
+ public LocalDate getDate() {
+ return date;
+ }
+
+ public void setDate(final LocalDate date) {
+ this.date = date;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CustomField.java b/src/main/java/com/p4square/ccbapi/model/CustomField.java
new file mode 100644
index 0000000..c9917e4
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CustomField.java
@@ -0,0 +1,74 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Representation of a Custom/User Defined Field.
+ */
+@XmlRootElement(name="custom_field")
+@XmlAccessorType(XmlAccessType.PROPERTY)
+public class CustomField {
+ private String name;
+ private String label;
+ private boolean adminOnly;
+
+ /**
+ * Create an empty CustomField object.
+ */
+ public CustomField() {
+ // Default constructor
+ }
+
+ /**
+ * Create a CustomField object with the name and label set.
+ *
+ * adminOnly will be false by default.
+ *
+ * @param name The CustomField name.
+ * @param label The CustomField label.
+ */
+ public CustomField(final String name, final String label) {
+ this(name, label, false);
+ }
+
+ /**
+ * Create a CustomField object with the name, label, and adminOnly fields set.
+ *
+ * @param name The CustomField name.
+ * @param label The CustomField label.
+ * @param adminOnly The value of the adminOnly field.
+ */
+ public CustomField(final String name, final String label, final boolean adminOnly) {
+ this.name = name;
+ this.label = label;
+ this.adminOnly = adminOnly;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public boolean isAdminOnly() {
+ return adminOnly;
+ }
+
+ @XmlElement(name="admin_only")
+ public void setAdminOnly(boolean adminOnly) {
+ this.adminOnly = adminOnly;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CustomFieldCollection.java b/src/main/java/com/p4square/ccbapi/model/CustomFieldCollection.java
new file mode 100644
index 0000000..0a1f87d
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CustomFieldCollection.java
@@ -0,0 +1,137 @@
+package com.p4square.ccbapi.model;
+
+import java.util.*;
+
+/**
+ * A collection of CustomField derivatives with indexes to find a custom field and value by either name or label.
+ *
+ * This collection will only ever contain one value for any given name or label.
+ */
+public class CustomFieldCollection<T extends CustomField> implements Collection<T> {
+
+ private final List<T> values;
+ private final Map<String, T> fieldLabelToValue;
+ private final Map<String, T> fieldNameToValue;
+
+ public CustomFieldCollection() {
+ this.values = new ArrayList<>();
+ this.fieldLabelToValue = new HashMap<>();
+ this.fieldNameToValue = new HashMap<>();
+ }
+
+ /**
+ * Return the entry with given name (e.g. "udf_text_1").
+ *
+ * @param name A CCB field name.
+ * @return The entry associated with the field.
+ */
+ public T getByName(final String name) {
+ return fieldNameToValue.get(name);
+ }
+
+ /**
+ * Return the entry with given label (e.g. "Favorite Book").
+ *
+ * @param label A CCB field label.
+ * @return The entry associated with the field.
+ */
+ public T getByLabel(final String label) {
+ return fieldLabelToValue.get(label);
+ }
+
+ public List<T> asList() {
+ return Collections.unmodifiableList(values);
+ }
+
+ @Override
+ public int size() {
+ return values.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return values.isEmpty();
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ return values.contains(o);
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return values.iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return values.toArray();
+ }
+
+ @Override
+ public <T1> T1[] toArray(final T1[] a) {
+ return values.toArray(a);
+ }
+
+ @Override
+ public boolean add(final T t) {
+ // Clean up overwritten indexes.
+ final T previousValueByName = fieldNameToValue.get(t.getName());
+ if (previousValueByName != null) {
+ remove(previousValueByName);
+ }
+
+ final T previousValueByLabel = fieldLabelToValue.get(t.getLabel());
+ if (previousValueByLabel != null) {
+ remove(previousValueByLabel);
+ }
+
+ fieldNameToValue.put(t.getName(), t);
+ fieldLabelToValue.put(t.getLabel(), t);
+ return values.add(t);
+ }
+
+ @Override
+ public boolean remove(final Object o) {
+ if (values.remove(o)) {
+ final T entry = (T) o;
+ fieldNameToValue.remove(entry.getName());
+ fieldLabelToValue.remove(entry.getLabel());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return values.containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(final Collection<? extends T> c) {
+ boolean result = false;
+ for (T obj : c) {
+ result |= add(obj);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean removeAll(final Collection<?> c) {
+ boolean result = false;
+ for (Object obj : c) {
+ result |= remove(obj);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean retainAll(final Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ values.clear();
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CustomPulldownFieldValue.java b/src/main/java/com/p4square/ccbapi/model/CustomPulldownFieldValue.java
new file mode 100644
index 0000000..02a962f
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CustomPulldownFieldValue.java
@@ -0,0 +1,23 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * A user-defined pulldown field and the associated value.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class CustomPulldownFieldValue extends CustomField {
+
+ @XmlElement(name="selection")
+ private PulldownSelection selection;
+
+ public PulldownSelection getSelection() {
+ return selection;
+ }
+
+ public void setSelection(final PulldownSelection selection) {
+ this.selection = selection;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/CustomTextFieldValue.java b/src/main/java/com/p4square/ccbapi/model/CustomTextFieldValue.java
new file mode 100644
index 0000000..71595e3
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/CustomTextFieldValue.java
@@ -0,0 +1,23 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * A user-defined text field and the associated value.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class CustomTextFieldValue extends CustomField {
+
+ @XmlElement(name="text")
+ private String text;
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(final String text) {
+ this.text = text;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/FamilyMemberReference.java b/src/main/java/com/p4square/ccbapi/model/FamilyMemberReference.java
new file mode 100644
index 0000000..212426c
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/FamilyMemberReference.java
@@ -0,0 +1,34 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * Reference to a family member.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class FamilyMemberReference {
+
+ @XmlElement(name="individual")
+ private IndividualReference individualReference;
+
+ @XmlElement(name="family_position")
+ private FamilyPosition familyPosition;
+
+ public IndividualReference getIndividualReference() {
+ return individualReference;
+ }
+
+ public void setIndividualReference(IndividualReference individualReference) {
+ this.individualReference = individualReference;
+ }
+
+ public FamilyPosition getFamilyPosition() {
+ return familyPosition;
+ }
+
+ public void setFamilyPosition(FamilyPosition familyPosition) {
+ this.familyPosition = familyPosition;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/FamilyPosition.java b/src/main/java/com/p4square/ccbapi/model/FamilyPosition.java
new file mode 100644
index 0000000..ecd1e37
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/FamilyPosition.java
@@ -0,0 +1,26 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlEnumValue;
+
+/**
+ * Enumeration of the supported values for the family_position field of an IndividualProfile.
+ */
+public enum FamilyPosition {
+ @XmlEnumValue("Primary Contact") PRIMARY_CONTACT("h"),
+ @XmlEnumValue("Spouse") SPOUSE("s"),
+ @XmlEnumValue("Child") CHILD("c"),
+ @XmlEnumValue("Other") OTHER("o");
+
+ private final String code;
+
+ FamilyPosition(String code) {
+ this.code = code;
+ }
+
+ /**
+ * @return A one character string representing the enum value.
+ */
+ public String getCode() {
+ return this.code;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/FamilyReference.java b/src/main/java/com/p4square/ccbapi/model/FamilyReference.java
new file mode 100644
index 0000000..0af220b
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/FamilyReference.java
@@ -0,0 +1,34 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * Reference to an family by id and name.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class FamilyReference {
+ @XmlAttribute(name="id")
+ private int familyId;
+
+ @XmlValue
+ private String name;
+
+ public int getFamilyId() {
+ return familyId;
+ }
+
+ public void setFamilyId(int familyId) {
+ this.familyId = familyId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/Gender.java b/src/main/java/com/p4square/ccbapi/model/Gender.java
new file mode 100644
index 0000000..eabaa42
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/Gender.java
@@ -0,0 +1,24 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlEnumValue;
+
+/**
+ * Enum representing the gender of an individual in CCB.
+ */
+public enum Gender {
+ @XmlEnumValue("M") MALE("M"),
+ @XmlEnumValue("F") FEMALE("F");
+
+ private final String code;
+
+ Gender(String code) {
+ this.code = code;
+ }
+
+ /**
+ * @return A one character string representing the enum value.
+ */
+ public String getCode() {
+ return this.code;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/GetCustomFieldLabelsResponse.java b/src/main/java/com/p4square/ccbapi/model/GetCustomFieldLabelsResponse.java
new file mode 100644
index 0000000..9f56036
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/GetCustomFieldLabelsResponse.java
@@ -0,0 +1,37 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GetCustomFieldLabelsResponse models the response returned by the custom_field_labels API.
+ */
+@XmlRootElement(name="response")
+@XmlAccessorType(XmlAccessType.NONE)
+public class GetCustomFieldLabelsResponse extends CCBAPIResponse {
+
+ @XmlElementWrapper(name = "custom_fields")
+ @XmlElement(name="custom_field")
+ private List<CustomField> customFields;
+
+ public GetCustomFieldLabelsResponse() {
+ customFields = new ArrayList<>();
+ }
+
+ /**
+ * @return The list of custom field names and labels.
+ */
+ public List<CustomField> getCustomFields() {
+ return customFields;
+ }
+
+ /**
+ * Set the list of custom fields.
+ *
+ * @param fields The list to include in the response.
+ */
+ public void setCustomFields(final List<CustomField> fields) {
+ this.customFields = fields;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesRequest.java b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesRequest.java
new file mode 100644
index 0000000..589de3c
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesRequest.java
@@ -0,0 +1,159 @@
+package com.p4square.ccbapi.model;
+
+import java.time.LocalDate;
+
+/**
+ * GetIndividualProfilesRequest is the set of options for retrieving individual profiles.
+ */
+public class GetIndividualProfilesRequest {
+
+ // Used with individual_profiles
+ private LocalDate modifiedSince;
+ private Boolean includeInactive;
+ private int page;
+ private int perPage;
+
+ // Used with individual_profile_from_id
+ private int id;
+
+ // Used with individual_profile_from_login_password
+ private String login;
+ private String password;
+
+ // Used with individual_profile_from_micr
+ private String routingNumber;
+ private String accountNumber;
+
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Request the IndividualProfile for the given individual id.
+ *
+ * This option is mutually exclusive with {@link #withLoginPassword(String, String)}
+ * and {@link #withMICR(String, String)}.
+ *
+ * @param id The id.
+ * @return this.
+ */
+ public GetIndividualProfilesRequest withIndividualId(final int id) {
+ this.id = id;
+ this.login = this.password = this.accountNumber = this.routingNumber = null;
+ return this;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Request the IndividualProfile for the given login and password.
+ *
+ * This option is mutually exclusive with {@link #withIndividualId(int)}
+ * and {@link #withMICR(String, String)}.
+ *
+ * @param login The individual's login.
+ * @param password The individual's password.
+ * @return this.
+ */
+ public GetIndividualProfilesRequest withLoginPassword(final String login, final String password) {
+ this.login = login;
+ this.password = password;
+ this.id = 0;
+ this.accountNumber = this.routingNumber = null;
+ return this;
+ }
+
+ public String getRoutingNumber() {
+ return routingNumber;
+ }
+
+ public String getAccountNumber() {
+ return accountNumber;
+ }
+
+ /**
+ * Request the IndividualProfile for the given bank account information.
+ *
+ * This option is mutually exclusive with {@link #withIndividualId(int)}
+ * and {@link #withLoginPassword(String, String)}.
+ *
+ * @param routingNumber The individual's bank routing number.
+ * @param accountNumber The individual's bank account number.
+ * @return this.
+ */
+ public GetIndividualProfilesRequest withMICR(final String routingNumber, final String accountNumber) {
+ this.routingNumber = routingNumber;
+ this.accountNumber = accountNumber;
+ return this;
+ }
+
+ public LocalDate getModifiedSince() {
+ return modifiedSince;
+ }
+
+ /**
+ * Request only IndividualProfiles modified since a given date.
+ *
+ * This option is only applicable when requesting all individuals.
+ *
+ * @param modifiedSince The date.
+ * @return this.
+ */
+ public GetIndividualProfilesRequest withModifiedSince(final LocalDate modifiedSince) {
+ this.modifiedSince = modifiedSince;
+ return this;
+ }
+
+ public Boolean getIncludeInactive() {
+ return includeInactive;
+ }
+
+ public GetIndividualProfilesRequest withIncludeInactive(final boolean includeInactive) {
+ this.includeInactive = includeInactive;
+ return this;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ /**
+ * Select the page of results when perPage is also specified.
+ *
+ * This option is only applicable when requesting all individuals.
+ *
+ * Defaults to 1 if {@link #withPerPage(int)} is specified on the request.
+ *
+ * @param page The starting page number.
+ * @return this.
+ */
+ public GetIndividualProfilesRequest withPage(final int page) {
+ this.page = page;
+ return this;
+ }
+
+ public int getPerPage() {
+ return perPage;
+ }
+
+ /**
+ * Limit the number of IndividualProfiles returned.
+ *
+ * This option is only applicable when requesting all individuals.
+ *
+ * Defaults to 25 if {@link #withPage(int)} is specified on the request.
+ *
+ * @param perPage The maximum number to return.
+ * @return this.
+ */
+ public GetIndividualProfilesRequest withPerPage(int perPage) {
+ this.perPage = perPage;
+ return this;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java
new file mode 100644
index 0000000..f88bcf7
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java
@@ -0,0 +1,27 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+import java.util.List;
+
+/**
+ * GetCustomFieldLabelsResponse models the response of a variety of APIs which return one or more Individual Profiles.
+ */
+@XmlRootElement(name="response")
+@XmlAccessorType(XmlAccessType.NONE)
+public class GetIndividualProfilesResponse extends CCBAPIResponse {
+
+ @XmlElementWrapper(name = "individuals")
+ @XmlElement(name="individual")
+ private List<IndividualProfile> individuals;
+
+ /**
+ * @return The list of individuals retrieved from CCB.
+ */
+ public List<IndividualProfile> getIndividuals() {
+ return individuals;
+ }
+
+ public void setIndividuals(List<IndividualProfile> individuals) {
+ this.individuals = individuals;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/IndividualProfile.java b/src/main/java/com/p4square/ccbapi/model/IndividualProfile.java
new file mode 100644
index 0000000..114d071
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/IndividualProfile.java
@@ -0,0 +1,449 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Representation of a Individual Profile.
+ */
+@XmlRootElement(name="individual")
+@XmlAccessorType(XmlAccessType.NONE)
+public class IndividualProfile {
+
+ @XmlAttribute(name="id")
+ private int id;
+
+ @XmlElement(name="other_id")
+ private int otherId;
+
+ @XmlElement(name="sync_id")
+ private int syncId;
+
+ @XmlElement(name="giving_number")
+ private String givingNumber;
+
+ @XmlElement(name="active")
+ private boolean active;
+
+ @XmlElement(name="first_name")
+ private String firstName;
+
+ @XmlElement(name="last_name")
+ private String lastName;
+
+ @XmlElement(name="middle_name")
+ private String middleName;
+
+ @XmlElement(name="legal_first_name")
+ private String legalFirstName;
+
+ @XmlElement(name="full_name")
+ private String fullName;
+
+ @XmlElement(name="salutation")
+ private String salutation;
+
+ @XmlElement(name="suffix")
+ private String suffix;
+
+ @XmlElement(name="image")
+ private String imageUrl;
+
+ @XmlElement(name="family_position", defaultValue = "Primary Contact")
+ private FamilyPosition familyPosition;
+
+ @XmlElement(name="family")
+ private FamilyReference family;
+
+ @XmlElement(name="family_image")
+ private String familyImageUrl;
+
+ @XmlElementWrapper(name="family_members")
+ @XmlElement(name="family_member")
+ private List<FamilyMemberReference> familyMembers;
+
+ @XmlElement(name="email")
+ private String email;
+
+ @XmlElement(name="login")
+ private String login;
+
+ @XmlElement(name="allergies")
+ private String allergies;
+
+ @XmlElement(name="confirmed_no_allergies")
+ private boolean confirmedNoAllergies;
+
+ @XmlElement(name="gender")
+ private Gender gender;
+
+ @XmlElement(name="marital_status", defaultValue="")
+ private MaritalStatus maritalStatus;
+
+ @XmlElement(name="birthday")
+ private LocalDate birthday;
+
+ @XmlElement(name="anniversary")
+ private LocalDate anniversary;
+
+ @XmlElement(name="deceased")
+ private LocalDate deceased;
+
+ @XmlElement(name="membership_date")
+ private LocalDate membershipStartDate;
+
+ @XmlElement(name="membership_end")
+ private LocalDate membershipEndDate;
+
+ @XmlElement(name="baptized")
+ private boolean baptized;
+
+ @XmlElement(name="creator")
+ private IndividualReference createdBy;
+
+ @XmlElement(name="created")
+ private LocalDateTime createdTime;
+
+ @XmlElement(name="modifier")
+ private IndividualReference modifiedBy;
+
+ @XmlElement(name="modified")
+ private LocalDateTime modifiedTime;
+
+ @XmlElementWrapper(name="addresses")
+ @XmlElement(name="address")
+ private List<Address> addresses;
+
+ @XmlElementWrapper(name="phones")
+ @XmlElement(name="phone")
+ private List<Phone> phones;
+
+ @XmlElementWrapper(name="user_defined_text_fields")
+ @XmlElement(name="user_defined_text_field")
+ private CustomFieldCollection<CustomTextFieldValue> customTextFields;
+
+ @XmlElementWrapper(name="user_defined_date_fields")
+ @XmlElement(name="user_defined_date_field")
+ private CustomFieldCollection<CustomDateFieldValue> customDateFields;
+
+ @XmlElementWrapper(name="user_defined_pulldown_fields")
+ @XmlElement(name="user_defined_pulldown_field")
+ private CustomFieldCollection<CustomPulldownFieldValue> customPulldownFields;
+
+ public IndividualProfile() {
+ familyMembers = new ArrayList<>();
+ addresses = new ArrayList<>();
+ phones = new ArrayList<>();
+ customTextFields = new CustomFieldCollection<>();
+ customDateFields = new CustomFieldCollection<>();
+ customPulldownFields = new CustomFieldCollection<>();
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getOtherId() {
+ return otherId;
+ }
+
+ public IndividualProfile setOtherId(int otherId) {
+ this.otherId = otherId;
+ return this;
+ }
+
+ public int getSyncId() {
+ return syncId;
+ }
+
+ public void setSyncId(int syncId) {
+ this.syncId = syncId;
+ }
+
+ public String getGivingNumber() {
+ return givingNumber;
+ }
+
+ public void setGivingNumber(String givingNumber) {
+ this.givingNumber = givingNumber;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ public void setMiddleName(String middleName) {
+ this.middleName = middleName;
+ }
+
+ public String getLegalFirstName() {
+ return legalFirstName;
+ }
+
+ public void setLegalFirstName(String legalFirstName) {
+ this.legalFirstName = legalFirstName;
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ public void setFullName(String fullName) {
+ this.fullName = fullName;
+ }
+
+ public String getSalutation() {
+ return salutation;
+ }
+
+ public void setSalutation(String salutation) {
+ this.salutation = salutation;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
+ }
+
+ public FamilyPosition getFamilyPosition() {
+ return familyPosition;
+ }
+
+ public void setFamilyPosition(FamilyPosition familyPosition) {
+ this.familyPosition = familyPosition;
+ }
+
+ public FamilyReference getFamily() {
+ return family;
+ }
+
+ public void setFamily(FamilyReference family) {
+ this.family = family;
+ }
+
+ public String getFamilyImageUrl() {
+ return familyImageUrl;
+ }
+
+ public void setFamilyImageUrl(String familyImageUrl) {
+ this.familyImageUrl = familyImageUrl;
+ }
+
+ public List<FamilyMemberReference> getFamilyMembers() {
+ return familyMembers;
+ }
+
+ public void setFamilyMembers(List<FamilyMemberReference> familyMembers) {
+ this.familyMembers = familyMembers;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public void setLogin(String login) {
+ this.login = login;
+ }
+
+ public String getAllergies() {
+ return allergies;
+ }
+
+ public void setAllergies(String allergies) {
+ this.allergies = allergies;
+ }
+
+ public boolean isConfirmedNoAllergies() {
+ return confirmedNoAllergies;
+ }
+
+ public void setConfirmedNoAllergies(boolean confirmedNoAllergies) {
+ this.confirmedNoAllergies = confirmedNoAllergies;
+ }
+
+ public Gender getGender() {
+ return gender;
+ }
+
+ public void setGender(Gender gender) {
+ this.gender = gender;
+ }
+
+ public MaritalStatus getMaritalStatus() {
+ return maritalStatus;
+ }
+
+ public void setMaritalStatus(MaritalStatus maritalStatus) {
+ this.maritalStatus = maritalStatus;
+ }
+
+ public LocalDate getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(LocalDate birthday) {
+ this.birthday = birthday;
+ }
+
+ public LocalDate getAnniversary() {
+ return anniversary;
+ }
+
+ public void setAnniversary(LocalDate anniversary) {
+ this.anniversary = anniversary;
+ }
+
+ public LocalDate getDeceased() {
+ return deceased;
+ }
+
+ public void setDeceased(LocalDate deceased) {
+ this.deceased = deceased;
+ }
+
+ public LocalDate getMembershipStartDate() {
+ return membershipStartDate;
+ }
+
+ public void setMembershipStartDate(LocalDate membershipStartDate) {
+ this.membershipStartDate = membershipStartDate;
+ }
+
+ public LocalDate getMembershipEndDate() {
+ return membershipEndDate;
+ }
+
+ public void setMembershipEndDate(LocalDate membershipEndDate) {
+ this.membershipEndDate = membershipEndDate;
+ }
+
+ public boolean isBaptized() {
+ return baptized;
+ }
+
+ public void setBaptized(boolean baptized) {
+ this.baptized = baptized;
+ }
+
+ public IndividualReference getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(IndividualReference createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public LocalDateTime getCreatedTime() {
+ return createdTime;
+ }
+
+ public void setCreatedTime(LocalDateTime createdTime) {
+ this.createdTime = createdTime;
+ }
+
+ public IndividualReference getModifiedBy() {
+ return modifiedBy;
+ }
+
+ public void setModifiedBy(IndividualReference modifiedBy) {
+ this.modifiedBy = modifiedBy;
+ }
+
+ public LocalDateTime getModifiedTime() {
+ return modifiedTime;
+ }
+
+ public void setModifiedTime(LocalDateTime modifiedTime) {
+ this.modifiedTime = modifiedTime;
+ }
+
+ public List<Address> getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List<Address> addresses) {
+ this.addresses = addresses;
+ }
+
+ public List<Phone> getPhones() {
+ return phones;
+ }
+
+ public void setPhones(List<Phone> phones) {
+ this.phones = phones;
+ }
+
+ public CustomFieldCollection<CustomTextFieldValue> getCustomTextFields() {
+ return customTextFields;
+ }
+
+ public void setCustomTextFields(CustomFieldCollection<CustomTextFieldValue> customTextFields) {
+ this.customTextFields = customTextFields;
+ }
+
+ public CustomFieldCollection<CustomDateFieldValue> getCustomDateFields() {
+ return customDateFields;
+ }
+
+ public void setCustomDateFields(CustomFieldCollection<CustomDateFieldValue> customDateFields) {
+ this.customDateFields = customDateFields;
+ }
+
+ public CustomFieldCollection<CustomPulldownFieldValue> getCustomPulldownFields() {
+ return customPulldownFields;
+ }
+
+ public void setCustomPulldownFields(CustomFieldCollection<CustomPulldownFieldValue> customPulldownFields) {
+ this.customPulldownFields = customPulldownFields;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/IndividualReference.java b/src/main/java/com/p4square/ccbapi/model/IndividualReference.java
new file mode 100644
index 0000000..1b25b90
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/IndividualReference.java
@@ -0,0 +1,34 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * Reference to an individual by id and name.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class IndividualReference {
+ @XmlAttribute(name="id")
+ private int individualId;
+
+ @XmlValue
+ private String name;
+
+ public int getIndividualId() {
+ return individualId;
+ }
+
+ public void setIndividualId(int individualId) {
+ this.individualId = individualId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/MaritalStatus.java b/src/main/java/com/p4square/ccbapi/model/MaritalStatus.java
new file mode 100644
index 0000000..d9e5921
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/MaritalStatus.java
@@ -0,0 +1,28 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlEnumValue;
+
+/**
+ * Enumeration of the possible values for the marital status field of an individual in CCB.
+ */
+public enum MaritalStatus {
+ @XmlEnumValue("Single") SINGLE("s"),
+ @XmlEnumValue("Married") MARRIED("m"),
+ @XmlEnumValue("Widowed") WIDOWED("w"),
+ @XmlEnumValue("Divorced") DIVORCED("d"),
+ @XmlEnumValue("Separated") SEPARATED("p"),
+ @XmlEnumValue("") NOT_SELECTED(" ");
+
+ private final String code;
+
+ MaritalStatus(String code) {
+ this.code = code;
+ }
+
+ /**
+ * @return A one character string representing the enum value.
+ */
+ public String getCode() {
+ return this.code;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/Phone.java b/src/main/java/com/p4square/ccbapi/model/Phone.java
new file mode 100644
index 0000000..eb66eb1
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/Phone.java
@@ -0,0 +1,41 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+
+/**
+ * Phone Number and Type pair.
+ */
+@XmlRootElement(name="phone")
+@XmlAccessorType(XmlAccessType.NONE)
+public class Phone {
+ @XmlType(namespace="Phone")
+ public enum Type {
+ @XmlEnumValue("contact") CONTACT,
+ @XmlEnumValue("home") HOME,
+ @XmlEnumValue("work") WORK,
+ @XmlEnumValue("mobile") MOBILE,
+ @XmlEnumValue("emergency") EMERGENCY;
+ }
+
+ @XmlAttribute(name="type")
+ private Type type;
+
+ @XmlValue
+ private String number;
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public String getNumber() {
+ return number;
+ }
+
+ public void setNumber(String number) {
+ this.number = number;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/PulldownSelection.java b/src/main/java/com/p4square/ccbapi/model/PulldownSelection.java
new file mode 100644
index 0000000..d473849
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/PulldownSelection.java
@@ -0,0 +1,34 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * A pull down field option.
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class PulldownSelection {
+ @XmlAttribute(name="id")
+ private int id;
+
+ @XmlValue
+ private String label;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(final int id) {
+ this.id = id;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(final String label) {
+ this.label = label;
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/package-info.java b/src/main/java/com/p4square/ccbapi/model/package-info.java
new file mode 100644
index 0000000..154bded
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/package-info.java
@@ -0,0 +1,16 @@
+/**
+ * This package contains models for CCB API requests and responses.
+ */
+@XmlJavaTypeAdapters({
+ @XmlJavaTypeAdapter(type=LocalDate.class, value=LocalDateXmlAdapter.class),
+ @XmlJavaTypeAdapter(type=LocalDateTime.class, value=LocalDateTimeXmlAdapter.class),
+})
+package com.p4square.ccbapi.model;
+
+import com.p4square.ccbapi.LocalDateTimeXmlAdapter;
+import com.p4square.ccbapi.LocalDateXmlAdapter;
+
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
+import java.time.LocalDate;
+import java.time.LocalDateTime;