summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java11
-rw-r--r--src/main/java/com/p4square/ccbapi/CCBAPI.java9
-rw-r--r--src/main/java/com/p4square/ccbapi/CCBAPIClient.java34
-rw-r--r--src/main/java/com/p4square/ccbapi/HTTPInterface.java4
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Countries.java8
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Country.java24
-rw-r--r--src/main/java/com/p4square/ccbapi/model/Gender.java11
-rw-r--r--src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java2
-rw-r--r--src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java378
-rw-r--r--src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java27
-rw-r--r--src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java96
-rw-r--r--src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java54
-rw-r--r--src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java125
-rw-r--r--src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java24
-rw-r--r--src/main/java/com/p4square/ccbapi/serializer/Serializer.java11
-rw-r--r--src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java63
-rw-r--r--src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java101
-rw-r--r--src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java203
-rw-r--r--src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java97
19 files changed, 1256 insertions, 26 deletions
diff --git a/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java b/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java
index 0833c67..4c3a777 100644
--- a/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java
+++ b/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java
@@ -9,6 +9,8 @@ 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.entity.ByteArrayEntity;
+import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
@@ -52,14 +54,13 @@ public class ApacheHttpClientImpl implements HTTPInterface {
}
@Override
- public InputStream sendPostRequest(URI uri, Map<String, String> form) throws IOException {
+ public InputStream sendPostRequest(final URI uri, final byte[] 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()));
+
+ if (form != null) {
+ httpPost.setEntity(new ByteArrayEntity(form));
}
- httpPost.setEntity(new UrlEncodedFormEntity(formParameters));
// Make the request.
final HttpResponse response = httpClient.execute(httpPost);
diff --git a/src/main/java/com/p4square/ccbapi/CCBAPI.java b/src/main/java/com/p4square/ccbapi/CCBAPI.java
index e54e20b..f81a02f 100644
--- a/src/main/java/com/p4square/ccbapi/CCBAPI.java
+++ b/src/main/java/com/p4square/ccbapi/CCBAPI.java
@@ -39,4 +39,13 @@ public interface CCBAPI extends Closeable {
* @throws IOException on failure.
*/
GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) throws IOException;
+
+ /**
+ * Update an IndividualProfile.
+ *
+ * @param request An UpdateIndividualProfileRequest including the fields to modify.
+ * @return An UpdateIndividualProfileResponse, including the updated IndividualProfile.
+ * @throws IOException on failure.
+ */
+ UpdateIndividualProfileResponse updateIndividualProfile(UpdateIndividualProfileRequest request) throws IOException;
}
diff --git a/src/main/java/com/p4square/ccbapi/CCBAPIClient.java b/src/main/java/com/p4square/ccbapi/CCBAPIClient.java
index ee309c6..404253a 100644
--- a/src/main/java/com/p4square/ccbapi/CCBAPIClient.java
+++ b/src/main/java/com/p4square/ccbapi/CCBAPIClient.java
@@ -2,11 +2,15 @@ package com.p4square.ccbapi;
import com.p4square.ccbapi.exception.CCBErrorResponseException;
import com.p4square.ccbapi.model.*;
+import com.p4square.ccbapi.serializer.AddressFormSerializer;
+import com.p4square.ccbapi.serializer.IndividualProfileSerializer;
+import com.p4square.ccbapi.serializer.PhoneFormSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -23,6 +27,8 @@ public class CCBAPIClient implements CCBAPI {
private static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
+ private static final IndividualProfileSerializer INDIVIDUAL_PROFILE_SERIALIZER = new IndividualProfileSerializer();
+
private final URI apiBaseUri;
private final HTTPInterface httpClient;
private final CCBXmlBinder xmlBinder;
@@ -119,12 +125,27 @@ public class CCBAPIClient implements CCBAPI {
}
// Send the request and parse the response.
- return makeRequest(serviceName, params, EMPTY_MAP, GetIndividualProfilesResponse.class);
+ return makeRequest(serviceName, params, null, GetIndividualProfilesResponse.class);
}
@Override
public GetCustomFieldLabelsResponse getCustomFieldLabels() throws IOException {
- return makeRequest("custom_field_labels", EMPTY_MAP, EMPTY_MAP, GetCustomFieldLabelsResponse.class);
+ return makeRequest("custom_field_labels", EMPTY_MAP, null, GetCustomFieldLabelsResponse.class);
+ }
+
+ @Override
+ public UpdateIndividualProfileResponse updateIndividualProfile(UpdateIndividualProfileRequest request)
+ throws IOException {
+
+ if (request.getIndividualId() == 0) {
+ throw new IllegalArgumentException("individualId must be set on the request.");
+ }
+
+ final Map<String, String> params = Collections.singletonMap("individual_id",
+ String.valueOf(request.getIndividualId()));
+ final String form = INDIVIDUAL_PROFILE_SERIALIZER.encode(request);
+
+ return makeRequest("update_individual", params, form, UpdateIndividualProfileResponse.class);
}
/**
@@ -164,10 +185,15 @@ public class CCBAPIClient implements CCBAPI {
* @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)
+ final String form, final Class<T> clazz)
throws IOException {
- final InputStream entity = httpClient.sendPostRequest(makeURI(api, params), form);
+ byte[] payload = null;
+ if (form != null) {
+ payload = form.getBytes(StandardCharsets.UTF_8);
+ }
+
+ final InputStream entity = httpClient.sendPostRequest(makeURI(api, params), payload);
try {
T response = xmlBinder.bindResponseXML(entity, clazz);
if (response.getErrors() != null && response.getErrors().size() > 0) {
diff --git a/src/main/java/com/p4square/ccbapi/HTTPInterface.java b/src/main/java/com/p4square/ccbapi/HTTPInterface.java
index 11d87ec..9c5e818 100644
--- a/src/main/java/com/p4square/ccbapi/HTTPInterface.java
+++ b/src/main/java/com/p4square/ccbapi/HTTPInterface.java
@@ -24,11 +24,11 @@ public interface HTTPInterface extends Closeable {
* 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.
+ * @param form Form data or null.
* @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;
+ InputStream sendPostRequest(URI uri, byte[] form) throws IOException;
}
diff --git a/src/main/java/com/p4square/ccbapi/model/Countries.java b/src/main/java/com/p4square/ccbapi/model/Countries.java
new file mode 100644
index 0000000..ec9df70
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/Countries.java
@@ -0,0 +1,8 @@
+package com.p4square.ccbapi.model;
+
+/**
+ * Constants which define common countries.
+ */
+public class Countries {
+ public static final Country UNITED_STATES = new Country("US", "United States");
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/Country.java b/src/main/java/com/p4square/ccbapi/model/Country.java
index e6936a5..ac9e9ec 100644
--- a/src/main/java/com/p4square/ccbapi/model/Country.java
+++ b/src/main/java/com/p4square/ccbapi/model/Country.java
@@ -16,6 +16,30 @@ public class Country {
private String name;
/**
+ * Default Country constructor.
+ */
+ public Country() {
+
+ }
+
+ /**
+ * This package-private Country constructor is used to create the
+ * constants in the {@link Countries} class.
+ *
+ * Usually the country name cannot be set except when binding to
+ * responses from CCB. This constructor is an exception so that the
+ * constants in {@link Countries} can be provide with
+ * <strong>known</strong> values used by CCB.
+ *
+ * @param code The two letter country code.
+ * @param name The country name as typically presented by CCB.
+ */
+ Country(final String code, final String name) {
+ setCountryCode(code);
+ this.name = name;
+ }
+
+ /**
* @return The two letter country code.
*/
public String getCountryCode() {
diff --git a/src/main/java/com/p4square/ccbapi/model/Gender.java b/src/main/java/com/p4square/ccbapi/model/Gender.java
index eabaa42..cf6736a 100644
--- a/src/main/java/com/p4square/ccbapi/model/Gender.java
+++ b/src/main/java/com/p4square/ccbapi/model/Gender.java
@@ -6,8 +6,15 @@ 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");
+ /**
+ * The documentation currently provides conflicting examples for the gender code.
+ * The documentation says it must be 'M' or 'F', but the example given uses 'm'.
+ * According to an API Village posting, it should actually be lower case.
+ *
+ * https://village.ccbchurch.com/message_comment_list.php?message_id=3308
+ */
+ @XmlEnumValue("M") MALE("m"),
+ @XmlEnumValue("F") FEMALE("f");
private final String code;
diff --git a/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java
index f88bcf7..bc7915c 100644
--- a/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java
+++ b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java
@@ -4,7 +4,7 @@ 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.
+ * GetIndividualProfilesResponse models the response of a variety of APIs which return one or more Individual Profiles.
*/
@XmlRootElement(name="response")
@XmlAccessorType(XmlAccessType.NONE)
diff --git a/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java
new file mode 100644
index 0000000..613b678
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java
@@ -0,0 +1,378 @@
+package com.p4square.ccbapi.model;
+
+import java.time.LocalDate;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * UpdateIndividualProfileRequest encapsulates a change to an IndividualProfile.
+ *
+ * The only property which must be set is {@link #withIndividualId(int)},
+ * to indicate the profile to update.
+ *
+ * A property can be unset by setting it to null.
+ */
+public class UpdateIndividualProfileRequest {
+
+ /**
+ * The set of all valid custom text field names.
+ */
+ public static final Set<String> CUSTOM_TEXT_FIELD_NAMES =
+ IntStream.rangeClosed(1, 12).mapToObj(x -> "udf_text_" + x).collect(Collectors.toSet());
+
+ /**
+ * The set of all valid custom date field names.
+ */
+ public static final Set<String> CUSTOM_DATE_FIELD_NAMES =
+ IntStream.rangeClosed(1, 6).mapToObj(x -> "udf_date_" + x).collect(Collectors.toSet());
+
+ /**
+ * The set of all valid custom pulldown field names.
+ */
+ public static final Set<String> CUSTOM_PULLDOWN_FIELD_NAMES =
+ IntStream.rangeClosed(1, 6).mapToObj(x -> "udf_pulldown_" + x).collect(Collectors.toSet());
+
+ private int individualId;
+
+ private Integer syncId;
+ private String otherId;
+ private String givingNumber;
+
+ private String firstName;
+ private String lastName;
+ private String middleName;
+ private String legalFirstName;
+ private String salutation;
+ private String suffix;
+
+ private Integer familyId;
+
+ private FamilyPosition familyPosition;
+
+ private MaritalStatus maritalStatus;
+
+ private Gender gender;
+ private LocalDate birthday;
+ private LocalDate anniversary;
+ private LocalDate deceased;
+ private LocalDate membershipDate;
+ private LocalDate membershipEnd;
+
+ private String email;
+
+ private List<Address> addresses;
+ private List<Phone> phones;
+
+ private String emergencyContactName;
+ private String allergies;
+ private Boolean confirmedNoAllergies;
+ private Boolean baptized;
+
+ private Map<String, String> customTextFields = new HashMap<>();
+ private Map<String, LocalDate> customDateFields = new HashMap<>();
+ private Map<String, Integer> customPulldownFields = new HashMap<>();
+
+ private Integer modifiedById;
+
+ public int getIndividualId() {
+ return individualId;
+ }
+
+ public UpdateIndividualProfileRequest withIndividualId(int individualId) {
+ this.individualId = individualId;
+ return this;
+ }
+
+ public Integer getSyncId() {
+ return syncId;
+ }
+
+ public UpdateIndividualProfileRequest withSyncId(Integer syncId) {
+ this.syncId = syncId;
+ return this;
+ }
+
+ public String getOtherId() {
+ return otherId;
+ }
+
+ public UpdateIndividualProfileRequest withOtherId(String otherId) {
+ this.otherId = otherId;
+ return this;
+ }
+
+ public String getGivingNumber() {
+ return givingNumber;
+ }
+
+ public UpdateIndividualProfileRequest withGivingNumber(String givingNumber) {
+ this.givingNumber = givingNumber;
+ return this;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public UpdateIndividualProfileRequest withFirstName(String firstName) {
+ this.firstName = firstName;
+ return this;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public UpdateIndividualProfileRequest withLastName(String lastName) {
+ this.lastName = lastName;
+ return this;
+ }
+
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ public UpdateIndividualProfileRequest withMiddleName(String middleName) {
+ this.middleName = middleName;
+ return this;
+ }
+
+ public String getLegalFirstName() {
+ return legalFirstName;
+ }
+
+ public UpdateIndividualProfileRequest withLegalFirstName(String legalFirstName) {
+ this.legalFirstName = legalFirstName;
+ return this;
+ }
+
+ public String getSalutation() {
+ return salutation;
+ }
+
+ public UpdateIndividualProfileRequest withSalutation(String salutation) {
+ this.salutation = salutation;
+ return this;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public UpdateIndividualProfileRequest withSuffix(String suffix) {
+ this.suffix = suffix;
+ return this;
+ }
+
+ public Integer getFamilyId() {
+ return familyId;
+ }
+
+ public UpdateIndividualProfileRequest withFamilyId(Integer familyId) {
+ this.familyId = familyId;
+ return this;
+ }
+
+ public FamilyPosition getFamilyPosition() {
+ return familyPosition;
+ }
+
+ public UpdateIndividualProfileRequest withFamilyPosition(FamilyPosition familyPosition) {
+ this.familyPosition = familyPosition;
+ return this;
+ }
+
+ public MaritalStatus getMaritalStatus() {
+ return maritalStatus;
+ }
+
+ public UpdateIndividualProfileRequest withMaritalStatus(MaritalStatus maritalStatus) {
+ this.maritalStatus = maritalStatus;
+ return this;
+ }
+
+ public Gender getGender() {
+ return gender;
+ }
+
+ public UpdateIndividualProfileRequest withGender(Gender gender) {
+ this.gender = gender;
+ return this;
+ }
+
+ public LocalDate getBirthday() {
+ return birthday;
+ }
+
+ public UpdateIndividualProfileRequest withBirthday(LocalDate birthday) {
+ this.birthday = birthday;
+ return this;
+ }
+
+ public LocalDate getAnniversary() {
+ return anniversary;
+ }
+
+ public UpdateIndividualProfileRequest withAnniversary(LocalDate anniversary) {
+ this.anniversary = anniversary;
+ return this;
+ }
+
+ public LocalDate getDeceased() {
+ return deceased;
+ }
+
+ public UpdateIndividualProfileRequest withDeceased(LocalDate deceased) {
+ this.deceased = deceased;
+ return this;
+ }
+
+ public LocalDate getMembershipDate() {
+ return membershipDate;
+ }
+
+ public UpdateIndividualProfileRequest withMembershipDate(LocalDate membershipDate) {
+ this.membershipDate = membershipDate;
+ return this;
+ }
+
+ public LocalDate getMembershipEnd() {
+ return membershipEnd;
+ }
+
+ public UpdateIndividualProfileRequest withMembershipEnd(LocalDate membershipEnd) {
+ this.membershipEnd = membershipEnd;
+ return this;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public UpdateIndividualProfileRequest withEmail(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public List<Address> getAddresses() {
+ return addresses;
+ }
+
+ public UpdateIndividualProfileRequest withAddresses(List<Address> addresses) {
+ this.addresses = addresses;
+ return this;
+ }
+
+ public List<Phone> getPhones() {
+ return phones;
+ }
+
+ public UpdateIndividualProfileRequest withPhones(List<Phone> phones) {
+ this.phones = phones;
+ return this;
+ }
+
+ public String getEmergencyContactName() {
+ return emergencyContactName;
+ }
+
+ public UpdateIndividualProfileRequest withEmergencyContactName(String emergencyContactName) {
+ this.emergencyContactName = emergencyContactName;
+ return this;
+ }
+
+ public String getAllergies() {
+ return allergies;
+ }
+
+ public UpdateIndividualProfileRequest withAllergies(String allergies) {
+ this.allergies = allergies;
+ return this;
+ }
+
+ public Boolean getConfirmedNoAllergies() {
+ return confirmedNoAllergies;
+ }
+
+ public UpdateIndividualProfileRequest withConfirmedNoAllergies(Boolean confirmedNoAllergies) {
+ this.confirmedNoAllergies = confirmedNoAllergies;
+ return this;
+ }
+
+ public Boolean getBaptized() {
+ return baptized;
+ }
+
+ public UpdateIndividualProfileRequest withBaptized(Boolean baptized) {
+ this.baptized = baptized;
+ return this;
+ }
+
+ /**
+ * @return A map of custom field identifiers to text field values.
+ */
+ public Map<String, String> getCustomTextFields() {
+ return customTextFields;
+ }
+
+ public UpdateIndividualProfileRequest withCustomTextField(final String name, final String value) {
+ if (!CUSTOM_TEXT_FIELD_NAMES.contains(name)) {
+ throw new IllegalArgumentException(name + " is not a valid a valid text field name.");
+ }
+ customTextFields.put(name, value);
+ return this;
+ }
+
+ public UpdateIndividualProfileRequest withCustomTextField(final int number, final String value) {
+ return withCustomTextField("udf_text_" + number, value);
+ }
+
+ /**
+ * @return A map of custom field identifiers to date field values.
+ */
+ public Map<String, LocalDate> getCustomDateFields() {
+ return customDateFields;
+ }
+
+ public UpdateIndividualProfileRequest withCustomDateField(final String name, final LocalDate value) {
+ if (!CUSTOM_DATE_FIELD_NAMES.contains(name)) {
+ throw new IllegalArgumentException(name + " is not a valid a valid date field name.");
+ }
+ customDateFields.put(name, value);
+ return this;
+ }
+
+ public UpdateIndividualProfileRequest withCustomDateField(final int number, final LocalDate value) {
+ return withCustomDateField("udf_date_" + number, value);
+ }
+
+ /**
+ * @return A map of custom field identifiers to pulldown field values.
+ */
+ public Map<String, Integer> getCustomPulldownFields() {
+ return customPulldownFields;
+ }
+
+ public UpdateIndividualProfileRequest withCustomPulldownField(final String name, final Integer value) {
+ if (!CUSTOM_PULLDOWN_FIELD_NAMES.contains(name)) {
+ throw new IllegalArgumentException(name + " is not a valid a valid pulldown field name.");
+ }
+ customPulldownFields.put(name, value);
+ return this;
+ }
+
+ public UpdateIndividualProfileRequest withCustomPulldownField(final int number, final Integer value) {
+ return withCustomPulldownField("udf_pulldown_" + number, value);
+ }
+
+ public Integer getModifiedById() {
+ return modifiedById;
+ }
+
+ public UpdateIndividualProfileRequest withModifiedById(Integer modifiedById) {
+ this.modifiedById = modifiedById;
+ return this;
+ }
+
+}
diff --git a/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java
new file mode 100644
index 0000000..9067d56
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java
@@ -0,0 +1,27 @@
+package com.p4square.ccbapi.model;
+
+import javax.xml.bind.annotation.*;
+import java.util.List;
+
+/**
+ * The response from an update_individual request.
+ */
+@XmlRootElement(name="response")
+@XmlAccessorType(XmlAccessType.NONE)
+public class UpdateIndividualProfileResponse 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/serializer/AbstractFormSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java
new file mode 100644
index 0000000..06a83a1
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java
@@ -0,0 +1,96 @@
+package com.p4square.ccbapi.serializer;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * AbstractFormSerializer provides default implementations for some methods defined in Serializer
+ * and methods to support encoding form data for CCB.
+ */
+public abstract class AbstractFormSerializer<T> implements Serializer<T> {
+
+ /**
+ * This is the datetime format specified by the CCB API Doc.
+ */
+ private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ public String encode(final T obj) {
+ final StringBuilder sb = new StringBuilder();
+ encode(obj, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Append a field to the form.
+ *
+ * @param builder The StringBuilder to use.
+ * @param key The form key, which must be URLEncoded before calling this method.
+ * @param value The value associated with the key. The value will be URLEncoded by this method.
+ */
+ protected void appendField(final StringBuilder builder, final String key, final String value) {
+ if (builder.length() > 0) {
+ builder.append("&");
+ }
+
+ try {
+ builder.append(key).append("=").append(URLEncoder.encode(value, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError("UTF-8 encoding should always be available.");
+ }
+ }
+
+ /**
+ * Append an integer field to the form.
+ *
+ * @param builder The StringBuilder to use.
+ * @param key The form key, which must be URLEncoded before calling this method.
+ * @param value The value associated with the key.
+ */
+ protected void appendField(final StringBuilder builder, final String key, final int value) {
+ if (builder.length() > 0) {
+ builder.append("&");
+ }
+ builder.append(key).append("=").append(value);
+ }
+
+ /**
+ * Append a boolean field to the form.
+ *
+ * @param builder The StringBuilder to use.
+ * @param key The form key, which must be URLEncoded before calling this method.
+ * @param value The value associated with the key.
+ */
+ protected void appendField(final StringBuilder builder, final String key, final boolean value) {
+ if (builder.length() > 0) {
+ builder.append("&");
+ }
+ builder.append(key).append("=").append(value ? "true" : "false");
+ }
+
+ /**
+ * Append a LocalDate field to the form.
+ *
+ * @param builder The StringBuilder to use.
+ * @param key The form key, which must be URLEncoded before calling this method.
+ * @param value The value associated with the key.
+ */
+ protected void appendField(final StringBuilder builder, final String key, final LocalDate value) {
+ appendField(builder, key, value.toString());
+
+ }
+
+ /**
+ * Append a LocalDateTime field to the form.
+ *
+ * @param builder The StringBuilder to use.
+ * @param key The form key, which must be URLEncoded before calling this method.
+ * @param value The value associated with the key.
+ */
+ protected void appendField(final StringBuilder builder, final String key, final LocalDateTime value) {
+ appendField(builder, key, DATE_TIME_FORMAT.format(value));
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java
new file mode 100644
index 0000000..00af004
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java
@@ -0,0 +1,54 @@
+package com.p4square.ccbapi.serializer;
+
+import com.p4square.ccbapi.model.Address;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * Encode an Address object as form data for CCB.
+ */
+public class AddressFormSerializer extends AbstractFormSerializer<Address> {
+
+ @Override
+ public void encode(final Address address, final StringBuilder builder) {
+ // Sanity check.
+ if (address.getType() == null) {
+ throw new IllegalArgumentException("Address type cannot be null");
+ }
+
+ // Every form field will be prefixed with the type.
+ final String type = address.getType().toString().toLowerCase();
+
+ if (address.getStreetAddress() != null) {
+ appendField(builder, type, "street_address", address.getStreetAddress());
+ }
+
+ if (address.getCity() != null) {
+ appendField(builder, type, "city", address.getCity());
+ }
+
+ if (address.getState() != null) {
+ appendField(builder, type, "state", address.getState());
+ }
+
+ if (address.getZip() != null) {
+ appendField(builder, type, "zip", address.getZip());
+ }
+
+ if (address.getCountry() != null) {
+ appendField(builder, type, "country", address.getCountry().getCountryCode());
+ }
+ }
+
+ private void appendField(final StringBuilder builder, final String type, final String key, final String value) {
+ if (builder.length() > 0) {
+ builder.append("&");
+ }
+ try {
+ builder.append(type).append("_").append(key).append("=").append(URLEncoder.encode(value, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError("UTF-8 encoding should always be available.");
+ }
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java
new file mode 100644
index 0000000..52b1a44
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java
@@ -0,0 +1,125 @@
+package com.p4square.ccbapi.serializer;
+
+import com.p4square.ccbapi.model.Address;
+import com.p4square.ccbapi.model.Phone;
+import com.p4square.ccbapi.model.UpdateIndividualProfileRequest;
+
+import java.time.LocalDate;
+import java.util.Map;
+
+/**
+ * Serializes an {@link UpdateIndividualProfileRequest} into a form suitable for the update_individual API.
+ */
+public class IndividualProfileSerializer extends AbstractFormSerializer<UpdateIndividualProfileRequest> {
+
+ private static final AddressFormSerializer ADDRESS_FORM_SERIALIZER = new AddressFormSerializer();
+ private static final PhoneFormSerializer PHONE_FORM_SERIALIZER = new PhoneFormSerializer();
+
+ @Override
+ public void encode(final UpdateIndividualProfileRequest request, final StringBuilder builder) {
+ // Encode any fields which are present.
+ if (request.getSyncId() != null) {
+ appendField(builder, "sync_id", request.getSyncId());
+ }
+ if (request.getOtherId() != null) {
+ appendField(builder, "other_id", request.getOtherId());
+ }
+ if (request.getGivingNumber() != null) {
+ appendField(builder, "giving_number", request.getGivingNumber());
+ }
+ if (request.getFirstName() != null) {
+ appendField(builder, "first_name", request.getFirstName());
+ }
+ if (request.getLastName() != null) {
+ appendField(builder, "last_name", request.getLastName());
+ }
+ if (request.getMiddleName() != null) {
+ appendField(builder, "middle_name", request.getMiddleName());
+ }
+ if (request.getLegalFirstName() != null) {
+ appendField(builder, "legal_first_name", request.getLegalFirstName());
+ }
+ if (request.getSalutation() != null) {
+ appendField(builder, "salutation", request.getSalutation());
+ }
+ if (request.getSuffix() != null) {
+ appendField(builder, "suffix", request.getSuffix());
+ }
+ if (request.getFamilyId() != null) {
+ appendField(builder, "family_id", request.getFamilyId());
+ }
+ if (request.getFamilyPosition() != null) {
+ appendField(builder, "family_position", request.getFamilyPosition().getCode());
+ }
+ if (request.getMaritalStatus() != null) {
+ appendField(builder, "marital_status", request.getMaritalStatus().getCode());
+ }
+ if (request.getGender() != null) {
+ appendField(builder, "gender", request.getGender().getCode());
+ }
+ if (request.getBirthday() != null) {
+ appendField(builder, "birthday", request.getBirthday());
+ }
+ if (request.getAnniversary() != null) {
+ appendField(builder, "anniversary", request.getAnniversary());
+ }
+ if (request.getDeceased() != null) {
+ appendField(builder, "deceased", request.getDeceased());
+ }
+ if (request.getMembershipDate() != null) {
+ appendField(builder, "membership_date", request.getMembershipDate());
+ }
+ if (request.getMembershipEnd() != null) {
+ appendField(builder, "membership_end", request.getMembershipEnd());
+ }
+ if (request.getEmail() != null) {
+ appendField(builder, "email", request.getEmail());
+ }
+ if (request.getEmergencyContactName() != null) {
+ appendField(builder, "emergency_contact_name", request.getEmergencyContactName());
+ }
+ if (request.getAllergies() != null) {
+ appendField(builder, "allergies", request.getAllergies());
+ }
+ if (request.getConfirmedNoAllergies() != null) {
+ appendField(builder, "confirmed_no_allergies", request.getConfirmedNoAllergies());
+ }
+ if (request.getBaptized() != null) {
+ appendField(builder, "baptized", request.getBaptized());
+ }
+ if (request.getModifiedById() != null) {
+ appendField(builder, "modifier_id", request.getModifiedById());
+ }
+
+ // Encode all the addresses.
+ if (request.getAddresses() != null) {
+ for (Address address : request.getAddresses()) {
+ ADDRESS_FORM_SERIALIZER.encode(address, builder);
+ }
+ }
+
+ // and the phone numbers.
+ if (request.getPhones() != null) {
+ for (Phone phone : request.getPhones()) {
+ PHONE_FORM_SERIALIZER.encode(phone, builder);
+ }
+ }
+
+ // Add the User-defined fields.
+ for (Map.Entry<String, String> entry : request.getCustomTextFields().entrySet()) {
+ if (entry.getValue() != null) {
+ appendField(builder, entry.getKey(), entry.getValue());
+ }
+ }
+ for (Map.Entry<String, LocalDate> entry : request.getCustomDateFields().entrySet()) {
+ if (entry.getValue() != null) {
+ appendField(builder, entry.getKey(), entry.getValue());
+ }
+ }
+ for (Map.Entry<String, Integer> entry : request.getCustomPulldownFields().entrySet()) {
+ if (entry.getValue() != null) {
+ appendField(builder, entry.getKey(), entry.getValue());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java
new file mode 100644
index 0000000..3569321
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java
@@ -0,0 +1,24 @@
+package com.p4square.ccbapi.serializer;
+
+import com.p4square.ccbapi.model.Phone;
+
+/**
+ * Encode a Phone object as form data for CCB.
+ */
+public class PhoneFormSerializer extends AbstractFormSerializer<Phone> {
+ @Override
+ public void encode(final Phone phone, final StringBuilder builder) {
+ // Sanity check.
+ if (phone.getType() == null) {
+ throw new IllegalArgumentException("Phone type cannot be null");
+ }
+
+ final String key;
+ if (phone.getType() == Phone.Type.EMERGENCY) {
+ key = "phone_emergency";
+ } else {
+ key = phone.getType().toString().toLowerCase() + "_phone";
+ }
+ appendField(builder, key, phone.getNumber());
+ }
+}
diff --git a/src/main/java/com/p4square/ccbapi/serializer/Serializer.java b/src/main/java/com/p4square/ccbapi/serializer/Serializer.java
new file mode 100644
index 0000000..775aa09
--- /dev/null
+++ b/src/main/java/com/p4square/ccbapi/serializer/Serializer.java
@@ -0,0 +1,11 @@
+package com.p4square.ccbapi.serializer;
+
+/**
+ * A Serializer converts an object of type T to a payload suitable for CCB.
+ */
+public interface Serializer<T> {
+
+ String encode(T obj);
+
+ void encode(T obj, StringBuilder builder);
+}
diff --git a/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java b/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java
index b15d16f..69f50c2 100644
--- a/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java
+++ b/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java
@@ -2,9 +2,7 @@ package com.p4square.ccbapi;
import com.p4square.ccbapi.exception.CCBErrorResponseException;
import com.p4square.ccbapi.exception.CCBRetryableErrorException;
-import com.p4square.ccbapi.model.GetCustomFieldLabelsResponse;
-import com.p4square.ccbapi.model.GetIndividualProfilesRequest;
-import com.p4square.ccbapi.model.GetIndividualProfilesResponse;
+import com.p4square.ccbapi.model.*;
import org.junit.Before;
import java.io.IOException;
@@ -16,6 +14,7 @@ import java.util.Map;
import org.easymock.EasyMock;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import static org.junit.Assert.*;
@@ -51,7 +50,7 @@ public class CCBAPIClientTest {
// Set expectation.
URI expectedURI = new URI("https://localhost:8080/api.php?srv=custom_field_labels");
InputStream is = getClass().getResourceAsStream("model/ccb_custom_field_labels_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -72,7 +71,7 @@ public class CCBAPIClientTest {
// Set expectation.
URI expectedURI = new URI("https://localhost:8080/api.php?srv=custom_field_labels");
InputStream is = getClass().getResourceAsStream("model/ccb_error_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -92,7 +91,7 @@ public class CCBAPIClientTest {
@Test(expected = CCBRetryableErrorException.class)
public void testGetCustomFieldLabelsTimeout() throws Exception {
// Setup mocks.
- EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject(Map.class)))
+ EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject()))
.andThrow(new CCBRetryableErrorException("Retryable error"));
EasyMock.replay(mockHttpClient);
@@ -103,7 +102,7 @@ public class CCBAPIClientTest {
@Test(expected = IOException.class)
public void testGetCustomFieldLabelsException() throws Exception {
// Setup mocks.
- EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject(Map.class)))
+ EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject()))
.andThrow(new IOException());
EasyMock.replay(mockHttpClient);
@@ -116,7 +115,7 @@ public class CCBAPIClientTest {
// Set expectation.
URI expectedURI = new URI("https://localhost:8080/api.php?srv=individual_profile_from_id&individual_id=48");
InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -137,7 +136,7 @@ public class CCBAPIClientTest {
URI expectedURI = new URI("https://localhost:8080/api.php?"
+ "srv=individual_profile_from_login_password&password=pass&login=user");
InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -159,7 +158,7 @@ public class CCBAPIClientTest {
URI expectedURI = new URI("https://localhost:8080/api.php?"
+ "srv=individual_profile_from_micr&account_number=4567&routing_number=1234");
InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -179,7 +178,7 @@ public class CCBAPIClientTest {
// Set expectation.
URI expectedURI = new URI("https://localhost:8080/api.php?srv=individual_profiles");
InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -200,7 +199,7 @@ public class CCBAPIClientTest {
URI expectedURI = new URI("https://localhost:8080/api.php?srv=individual_profiles"
+ "&per_page=15&include_inactive=true&modified_since=2016-03-19&page=5");
InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml");
- EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.<String, String>emptyMap()))
+ EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null))
.andReturn(is);
EasyMock.replay(mockHttpClient);
@@ -219,6 +218,46 @@ public class CCBAPIClientTest {
assertEquals(48, response.getIndividuals().get(0).getId());
}
+ @Test
+ public void testUpdateIndividual() throws Exception {
+ // Set expectation.
+ URI expectedURI = new URI("https://localhost:8080/api.php?srv=update_individual&individual_id=48");
+ InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml");
+ EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.eq(expectedURI),
+ EasyMock.aryEq("legal_first_name=Larabar".getBytes("UTF-8"))))
+ .andReturn(is);
+ EasyMock.replay(mockHttpClient);
+
+ UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withIndividualId(48)
+ .withLegalFirstName("Larabar");
+
+ // Test update_individual.
+ UpdateIndividualProfileResponse response = client.updateIndividualProfile(request);
+
+ // Verify results.
+ EasyMock.verify(mockHttpClient);
+ assertNull(response.getErrors());
+ assertEquals(1, response.getIndividuals().size());
+ assertEquals(48, response.getIndividuals().get(0).getId());
+ }
+
+ /**
+ * This test ensures {@link CCBAPIClient#updateIndividualProfile(UpdateIndividualProfileRequest)}
+ * throws an exception if the individualId property isn't set on the request.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateIndividualWithoutIndividualId() throws Exception {
+ // Set expectation.
+ UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withLegalFirstName("Larabar");
+
+ // Test update_individual.
+ UpdateIndividualProfileResponse response = client.updateIndividualProfile(request);
+
+ // Expect exception.
+ }
+
/**
* Simple extension of CCBAPIClient to swap out the HTTPInterface with a mock.
*/
diff --git a/src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java b/src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java
new file mode 100644
index 0000000..0275358
--- /dev/null
+++ b/src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java
@@ -0,0 +1,101 @@
+package com.p4square.ccbapi.serializer;
+
+import com.p4square.ccbapi.model.Address;
+import com.p4square.ccbapi.model.Countries;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the AddressFormSerializer.
+ *
+ * Serializer output is compared to the expected output in the update_individual API example.
+ */
+public class AddressFormSerializerTest {
+
+ private AddressFormSerializer serializer;
+
+ @Before
+ public void setUp() {
+ serializer = new AddressFormSerializer();
+ }
+
+ @Test
+ public void testEncodeAllFields() {
+ final Address address = new Address();
+ address.setType(Address.Type.MAILING);
+ address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+ address.setCity("Colorado Springs");
+ address.setState("CO");
+ address.setZip("80921");
+ address.setCountry(Countries.UNITED_STATES);
+
+ final String actual = serializer.encode(address);
+
+ assertEquals("mailing_street_address=12265+Oracle+Blvd%2C+Suite+105"
+ + "&mailing_city=Colorado+Springs&mailing_state=CO&mailing_zip=80921&mailing_country=US",
+ actual);
+ }
+
+ @Test
+ public void testEncodeSomeFields() {
+ final Address address = new Address();
+ address.setType(Address.Type.MAILING);
+ address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+
+ final String actual = serializer.encode(address);
+
+ assertEquals("mailing_street_address=12265+Oracle+Blvd%2C+Suite+105", actual);
+ }
+
+ @Test
+ public void testEncodeSomeFieldsWithExistingData() {
+ final StringBuilder sb = new StringBuilder("foo=bar");
+
+ final Address address = new Address();
+ address.setType(Address.Type.MAILING);
+ address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+ address.setCity("Colorado Springs");
+
+ serializer.encode(address, sb);
+ final String actual = sb.toString();
+
+ assertEquals("foo=bar&mailing_street_address=12265+Oracle+Blvd%2C+Suite+105&mailing_city=Colorado+Springs",
+ actual);
+ }
+
+ @Test
+ public void testEncodeHomeAddress() {
+ final Address address = new Address();
+ address.setType(Address.Type.HOME);
+ address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+
+ final String actual = serializer.encode(address);
+
+ assertEquals("home_street_address=12265+Oracle+Blvd%2C+Suite+105", actual);
+ }
+
+ @Test
+ public void testEncodeWorkAddress() {
+ final Address address = new Address();
+ address.setType(Address.Type.WORK);
+ address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+
+ final String actual = serializer.encode(address);
+
+ assertEquals("work_street_address=12265+Oracle+Blvd%2C+Suite+105", actual);
+ }
+
+ @Test
+ public void testEncodeOtherAddress() {
+ final Address address = new Address();
+ address.setType(Address.Type.OTHER);
+ address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+
+ final String actual = serializer.encode(address);
+
+ assertEquals("other_street_address=12265+Oracle+Blvd%2C+Suite+105", actual);
+ }
+
+} \ No newline at end of file
diff --git a/src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java b/src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java
new file mode 100644
index 0000000..a1bb8c9
--- /dev/null
+++ b/src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java
@@ -0,0 +1,203 @@
+package com.p4square.ccbapi.serializer;
+
+import com.p4square.ccbapi.model.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the IndividualProfileSerializer.
+ *
+ * Serializer output is compared to the expected output in the update_individual API example.
+ */
+public class IndividualProfileSerializerTest {
+
+ private IndividualProfileSerializer serializer;
+
+ @Before
+ public void setUp() {
+ serializer = new IndividualProfileSerializer();
+ }
+
+ /**
+ * This test sets all of the fields on an
+ * {@link UpdateIndividualProfileRequest} and tries to serialize them.
+ */
+ @Test
+ public void testEncodeAllFields() {
+ final Address mailing_address = new Address();
+ mailing_address.setType(Address.Type.MAILING);
+ mailing_address.setStreetAddress("12265 Oracle Blvd, Suite 105");
+ mailing_address.setCity("Colorado Springs");
+ mailing_address.setState("CO");
+ mailing_address.setZip("80921");
+ mailing_address.setCountry(Countries.UNITED_STATES);
+
+ final Phone phone = new Phone();
+ phone.setType(Phone.Type.CONTACT);
+ phone.setNumber("719-555-2888");
+
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withSyncId(1001)
+ .withOtherId("A1001")
+ .withGivingNumber("ABC123")
+ .withFirstName("Larry")
+ .withLastName("Bob")
+ .withMiddleName("P")
+ .withLegalFirstName("Larabar")
+ .withSalutation("Mr.")
+ .withSuffix("Jr.")
+ .withFamilyId(48)
+ .withFamilyPosition(FamilyPosition.PRIMARY_CONTACT)
+ .withMaritalStatus(MaritalStatus.MARRIED)
+ .withGender(Gender.MALE)
+ .withBirthday(LocalDate.of(1966, 2, 12))
+ .withAnniversary(LocalDate.of(1989, 5, 6))
+ .withDeceased(LocalDate.of(2016, 12, 31))
+ .withMembershipDate(LocalDate.of(2010, 1, 1))
+ .withMembershipEnd(LocalDate.of(2016, 12, 1))
+ .withEmail("ken@example.com")
+ .withAddresses(Collections.singletonList(mailing_address))
+ .withPhones(Collections.singletonList(phone))
+ .withEmergencyContactName("Marry Bob")
+ .withAllergies("Pine Nuts")
+ .withConfirmedNoAllergies(true)
+ .withBaptized(true)
+ .withModifiedById(12);
+
+ final String actual = serializer.encode(request);
+
+ final String expected = "sync_id=1001&other_id=A1001&giving_number=ABC123"
+ + "&first_name=Larry&last_name=Bob&middle_name=P&legal_first_name=Larabar&salutation=Mr."
+ + "&suffix=Jr.&family_id=48&family_position=h&marital_status=m&gender=m"
+ + "&birthday=1966-02-12&anniversary=1989-05-06&deceased=2016-12-31"
+ + "&membership_date=2010-01-01&membership_end=2016-12-01"
+ + "&email=ken%40example.com&emergency_contact_name=Marry+Bob"
+ + "&allergies=Pine+Nuts&confirmed_no_allergies=true&baptized=true&modifier_id=12"
+ + "&mailing_street_address=12265+Oracle+Blvd%2C+Suite+105"
+ + "&mailing_city=Colorado+Springs&mailing_state=CO&mailing_zip=80921&mailing_country=US"
+ + "&contact_phone=719-555-2888";
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testEncodeSomeFields() {
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withSyncId(1001)
+ .withMiddleName("P")
+ .withMaritalStatus(MaritalStatus.DIVORCED)
+ .withGender(Gender.FEMALE)
+ .withBirthday(LocalDate.of(1966, 2, 12));
+
+ final String actual = serializer.encode(request);
+
+ assertEquals("sync_id=1001&middle_name=P&marital_status=d&gender=f&birthday=1966-02-12", actual);
+ }
+
+ @Test
+ public void testEncodeMultipleAddresses() {
+ final Address address1 = new Address();
+ address1.setType(Address.Type.HOME);
+ address1.setStreetAddress("123 Happy Lane");
+
+ final Address address2 = new Address();
+ address2.setType(Address.Type.WORK);
+ address2.setStreetAddress("12265 Oracle Blvd, Suite 105");
+
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withAddresses(Arrays.asList(address1, address2));
+
+ final String actual = serializer.encode(request);
+
+ assertEquals("home_street_address=123+Happy+Lane&work_street_address=12265+Oracle+Blvd%2C+Suite+105", actual);
+ }
+
+ @Test
+ public void testEncodeMultiplePhones() {
+ final Phone phone1 = new Phone();
+ phone1.setType(Phone.Type.MOBILE);
+ phone1.setNumber("719-555-2888");
+
+ final Phone phone2 = new Phone();
+ phone2.setType(Phone.Type.EMERGENCY);
+ phone2.setNumber("719-555-3999");
+
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withPhones(Arrays.asList(phone1, phone2));
+
+ final String actual = serializer.encode(request);
+
+ assertEquals("mobile_phone=719-555-2888&phone_emergency=719-555-3999", actual);
+ }
+
+ @Test
+ public void testEncodeCustomFieldsByName() {
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withCustomTextField("udf_text_1", "Hello")
+ .withCustomDateField("udf_date_1", LocalDate.of(1966, 2, 12))
+ .withCustomPulldownField("udf_pulldown_1", 12);
+
+ final String actual = serializer.encode(request);
+
+ assertEquals("udf_text_1=Hello&udf_date_1=1966-02-12&udf_pulldown_1=12", actual);
+ }
+
+ @Test
+ public void testEncodeCustomFieldsByNumber() {
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withCustomTextField(2, "Hello")
+ .withCustomDateField(2, LocalDate.of(1966, 2, 12))
+ .withCustomPulldownField(2, 12);
+
+ final String actual = serializer.encode(request);
+
+ assertEquals("udf_text_2=Hello&udf_date_2=1966-02-12&udf_pulldown_2=12", actual);
+ }
+
+ @Test
+ public void testNullCustomField() {
+ final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest()
+ .withCustomTextField(1, null)
+ .withCustomDateField(1, null)
+ .withCustomPulldownField(1, null);
+
+ final String actual = serializer.encode(request);
+ assertEquals("", actual);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidCustomTextFieldName() {
+ new UpdateIndividualProfileRequest().withCustomTextField("udf_text_13", "foo");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidCustomTextFieldNumber() {
+ new UpdateIndividualProfileRequest().withCustomTextField(0, "foo");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidCustomDateFieldName() {
+ new UpdateIndividualProfileRequest().withCustomDateField("udf_date_7", LocalDate.of(1966, 2, 12));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidCustomDateFieldNumber() {
+ new UpdateIndividualProfileRequest().withCustomDateField(0, LocalDate.of(1966, 2, 12));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidCustomPulldownFieldName() {
+ new UpdateIndividualProfileRequest().withCustomPulldownField("udf_pulldown_7", 12);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidCustomPulldownFieldNumber() {
+ new UpdateIndividualProfileRequest().withCustomPulldownField(0, 12);
+ }
+} \ No newline at end of file
diff --git a/src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java b/src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java
new file mode 100644
index 0000000..7074ad3
--- /dev/null
+++ b/src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java
@@ -0,0 +1,97 @@
+package com.p4square.ccbapi.serializer;
+
+import com.p4square.ccbapi.model.Phone;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the PhoneFormSerializer.
+ *
+ * Serializer output is compared to the expected output in the update_individual API example.
+ */
+public class PhoneFormSerializerTest {
+
+ private PhoneFormSerializer serializer;
+
+ @Before
+ public void setUp() {
+ serializer = new PhoneFormSerializer();
+ }
+
+ @Test
+ public void testEncodeContactPhone() {
+ final Phone phone = new Phone();
+ phone.setType(Phone.Type.CONTACT);
+ phone.setNumber("719-555-2888");
+
+ final String actual = serializer.encode(phone);
+
+ assertEquals("contact_phone=719-555-2888", actual);
+ }
+
+ @Test
+ public void testEncodeHomePhone() {
+ final Phone phone = new Phone();
+ phone.setType(Phone.Type.HOME);
+ phone.setNumber("719-555-2888");
+
+ final String actual = serializer.encode(phone);
+
+ assertEquals("home_phone=719-555-2888", actual);
+ }
+
+ @Test
+ public void testEncodeWorkPhone() {
+ final Phone phone = new Phone();
+ phone.setType(Phone.Type.WORK);
+ phone.setNumber("719-555-2888");
+
+ final String actual = serializer.encode(phone);
+
+ assertEquals("work_phone=719-555-2888", actual);
+ }
+
+ @Test
+ public void testEncodeMobilePhone() {
+ final Phone phone = new Phone();
+ phone.setType(Phone.Type.MOBILE);
+ phone.setNumber("719-555-2888");
+
+ final String actual = serializer.encode(phone);
+
+ assertEquals("mobile_phone=719-555-2888", actual);
+ }
+
+ @Test
+ public void testEncodeEmergencyPhone() {
+ final Phone phone = new Phone();
+ phone.setType(Phone.Type.EMERGENCY);
+ phone.setNumber("719-555-2888");
+
+ final String actual = serializer.encode(phone);
+
+ assertEquals("phone_emergency=719-555-2888", actual);
+ }
+
+ @Test
+ public void testEncodeWithExistingData() {
+ final StringBuilder sb = new StringBuilder();
+
+ final Phone phone1 = new Phone();
+ phone1.setType(Phone.Type.MOBILE);
+ phone1.setNumber("719-555-2888");
+
+ final Phone phone2 = new Phone();
+ phone2.setType(Phone.Type.EMERGENCY);
+ phone2.setNumber("719-555-3999");
+
+ serializer.encode(phone1, sb);
+ serializer.encode(phone2, sb);
+ final String actual = sb.toString();
+
+ assertEquals("mobile_phone=719-555-2888&phone_emergency=719-555-3999", actual);
+ }
+
+} \ No newline at end of file