summaryrefslogtreecommitdiff
path: root/src/main/java/com/p4square/ccbapi/CCBAPIClient.java
blob: 782f30575e720e451d8d0912daed7a6927ae5936 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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();
            }
        }
    }
}