11/**
22 * Copyright 2015 Brightcove Inc. All rights reserved.
3- *
4- * @author Scott Kidder
53 */
64package com .brightcove .castlabs .client ;
75
86import java .io .IOException ;
9- import java .util .ArrayList ;
107import java .util .List ;
118
9+ import com .google .common .collect .Lists ;
1210import org .apache .commons .io .IOUtils ;
11+ import org .apache .commons .lang .StringUtils ;
1312import org .apache .http .Header ;
1413import org .apache .http .HttpEntity ;
1514import org .apache .http .NameValuePair ;
2423
2524import com .brightcove .castlabs .client .request .IngestKeysRequest ;
2625import com .brightcove .castlabs .client .response .IngestAssetsResponse ;
26+ import com .brightcove .castlabs .client .request .AddSubMerchantAccountRequest ;
27+ import com .brightcove .castlabs .client .response .AddSubMerchantAccountResponse ;
2728import com .fasterxml .jackson .databind .DeserializationFeature ;
2829import com .fasterxml .jackson .databind .ObjectMapper ;
2930
3031/**
31- * Client for interacting with the Castlabs key ingestion API.
32- *
33- * @author Scott Kidder
34- *
32+ * Client for interacting with the Castlabs API.
3533 */
3634public class CastlabsClient {
3735
@@ -44,12 +42,11 @@ public class CastlabsClient {
4442private int connectionTimeoutSeconds = -1 ;
4543private ObjectMapper objectMapper ;
4644
47- public CastlabsClient (String username ,String password ) {
45+ public CastlabsClient (final String username ,final String password ) {
4846this (username ,password ,CASTLABS_AUTH_BASE_URL ,CASTLABS_INGESTION_BASE_URL , -1 );
4947 }
5048
51- public CastlabsClient (String username ,String password ,String authBaseUrl ,
52- String ingestionBaseUrl ,int connectionTimeoutSeconds ) {
49+ public CastlabsClient (final String username ,final String password ,final String authBaseUrl ,final String ingestionBaseUrl ,final int connectionTimeoutSeconds ) {
5350this .objectMapper =new ObjectMapper ()
5451 .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES ,false );
5552this .username =username ;
@@ -71,33 +68,32 @@ public CastlabsClient(String username, String password, String authBaseUrl,
7168
7269/**
7370 * Login to the Castlabs API endpoint.
74- *
71+ *
7572 * @return a ticket URL
7673 * @throws CastlabsException error reported by Castlabs
77- * @throws IOException communication error when interacting with Castlabs API
74+ * @throws IOException communication error when interacting with Castlabs API
7875 */
7976protected String login ()throws CastlabsException ,IOException {
8077final HttpPost loginRequest =new HttpPost (this .authBaseUrl +"cas/v1/tickets" );
8178loginRequest .addHeader ("Content-Type" ,"application/x-www-form-urlencoded" );
8279loginRequest .setHeader ("Accept" ,"*/*" );
8380
84- final List <NameValuePair >entityParts =new ArrayList < NameValuePair > ();
81+ final List <NameValuePair >entityParts =Lists . newArrayList ();
8582entityParts .add (new BasicNameValuePair ("username" ,this .username ));
8683entityParts .add (new BasicNameValuePair ("password" ,this .password ));
8784
85+ if (this .connectionTimeoutSeconds >0 ) {
86+ final int connectionTimeout =connectionTimeoutSeconds *1000 ;
87+ final RequestConfig requestConfig =
88+ RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
89+ .setConnectTimeout (connectionTimeout )
90+ .setSocketTimeout (connectionTimeout ).build ();
91+ loginRequest .setConfig (requestConfig );
92+ }
93+ loginRequest .setEntity (new UrlEncodedFormEntity (entityParts ));
94+
8895final CloseableHttpClient httpclient =HttpClients .createDefault ();
89- CloseableHttpResponse loginResponse =null ;
90- try {
91- if (this .connectionTimeoutSeconds >0 ) {
92- int connectionTimeout =connectionTimeoutSeconds *1000 ;
93- RequestConfig requestConfig =
94- RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
95- .setConnectTimeout (connectionTimeout )
96- .setSocketTimeout (connectionTimeout ).build ();
97- loginRequest .setConfig (requestConfig );
98- }
99- loginRequest .setEntity (new UrlEncodedFormEntity (entityParts ));
100- loginResponse =httpclient .execute (loginRequest );
96+ try (final CloseableHttpResponse loginResponse =httpclient .execute (loginRequest )) {
10197if (loginResponse !=null ) {
10298final int statusCode =loginResponse .getStatusLine ().getStatusCode ();
10399final String reason =loginResponse .getStatusLine ().getReasonPhrase ();
@@ -115,47 +111,38 @@ protected String login() throws CastlabsException, IOException {
115111 }else {
116112throw new CastlabsException ("No location header provided in API response" );
117113 }
118- }finally {
119- try {
120- if (loginResponse !=null )
121- loginResponse .close ();
122- }catch (IOException e ) {
123- // ignore
124- }
125114 }
126115 }
127116
128117
129118/**
130- * Retrieve an authentication ticket with the givenmerchant ID.
131- *
132- * @parammerchantId Castlabs-issued merchant ID
119+ * Retrieve an authentication ticket with the givenURL and return the URL with token appended
120+ *
121+ * @paramurl URL to request the token for
133122 * @return ticket that can be used to ingest encryption keys
134123 * @throws CastlabsException error reported by Castlabs
135- * @throws IOException communication error when interacting with Castlabs API
124+ * @throws IOException communication error when interacting with Castlabs API
136125 */
137- protected String getTicket ( String merchantId )throws CastlabsException ,IOException {
126+ protected String getUrlWithTicket ( final String url )throws CastlabsException ,IOException {
138127final HttpPost ticketRequest =new HttpPost (this .login ());
139128ticketRequest .addHeader ("Content-Type" ,"application/x-www-form-urlencoded" );
140129ticketRequest .setHeader ("Accept" ,"*/*" );
141130
142- final List <NameValuePair >entityParts =new ArrayList <NameValuePair >();
143- entityParts .add (new BasicNameValuePair ("service" ,
144- this .ingestionBaseUrl +"frontend/api/keys/v2/ingest/" +merchantId ));
131+ final List <NameValuePair >entityParts =Lists .newArrayList ();
132+ entityParts .add (new BasicNameValuePair ("service" ,url ));
133+
134+ if (this .connectionTimeoutSeconds >0 ) {
135+ final int connectionTimeout =connectionTimeoutSeconds *1000 ;
136+ final RequestConfig requestConfig =
137+ RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
138+ .setConnectTimeout (connectionTimeout )
139+ .setSocketTimeout (connectionTimeout ).build ();
140+ ticketRequest .setConfig (requestConfig );
141+ }
142+ ticketRequest .setEntity (new UrlEncodedFormEntity (entityParts ));
145143
146144final CloseableHttpClient httpclient =HttpClients .createDefault ();
147- CloseableHttpResponse ticketResponse =null ;
148- try {
149- if (this .connectionTimeoutSeconds >0 ) {
150- int connectionTimeout =connectionTimeoutSeconds *1000 ;
151- RequestConfig requestConfig =
152- RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
153- .setConnectTimeout (connectionTimeout )
154- .setSocketTimeout (connectionTimeout ).build ();
155- ticketRequest .setConfig (requestConfig );
156- }
157- ticketRequest .setEntity (new UrlEncodedFormEntity (entityParts ));
158- ticketResponse =httpclient .execute (ticketRequest );
145+ try (final CloseableHttpResponse ticketResponse =httpclient .execute (ticketRequest )) {
159146if (ticketResponse !=null ) {
160147final int statusCode =ticketResponse .getStatusLine ().getStatusCode ();
161148if (200 !=statusCode ) {
@@ -168,47 +155,39 @@ protected String getTicket(String merchantId) throws CastlabsException, IOExcept
168155 }else {
169156throw new CastlabsException ("No response when retrieving Castlabs ticket" );
170157 }
171- return IOUtils .toString (ticketResponse .getEntity ().getContent ());
172- }finally {
173- try {
174- if (ticketResponse !=null )
175- ticketResponse .close ();
176- }catch (IOException e ) {
177- // ignore
178- }
158+ return url +"?ticket=" +IOUtils .toString (ticketResponse .getEntity ().getContent ());
179159 }
180160 }
181161
182162/**
183163 * Ingest one or more keys into the Castlabs keystore.
184- *
185- * @param request
164+ *
165+ * @param request Request parameters to pass to Castlabs
186166 * @param merchantId
187167 * @return response from Castlabs
188168 * @throws CastlabsException error reported by Castlabs
189- * @throws IOException network error while communicating with Castlabs REST API
169+ * @throws IOException network error while communicating with Castlabs REST API
190170 */
191- public IngestAssetsResponse ingestKeys (IngestKeysRequest request ,String merchantId )
171+ public IngestAssetsResponse ingestKeys (final IngestKeysRequest request ,final String merchantId )
192172throws CastlabsException ,IOException {
193- final String uri = this . ingestionBaseUrl + "frontend/api/keys/v2/ingest/" + merchantId
194- +"?ticket= " +this . getTicket ( merchantId );
173+
174+ final String uri = this . getUrlWithTicket ( this . ingestionBaseUrl +"frontend/api/keys/v2/ingest/ " +merchantId );
195175final HttpPost ingestRequest =new HttpPost (uri );
196176ingestRequest .addHeader ("Content-Type" ,"application/json" );
197177ingestRequest .setHeader ("Accept" ,"application/json" );
198178
179+ if (this .connectionTimeoutSeconds >0 ) {
180+ final int connectionTimeout =connectionTimeoutSeconds *1000 ;
181+ final RequestConfig requestConfig =
182+ RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
183+ .setConnectTimeout (connectionTimeout )
184+ .setSocketTimeout (connectionTimeout ).build ();
185+ ingestRequest .setConfig (requestConfig );
186+ }
187+ ingestRequest .setEntity (new StringEntity (objectMapper .writeValueAsString (request )));
188+
199189final CloseableHttpClient httpclient =HttpClients .createDefault ();
200- CloseableHttpResponse ingestResponse =null ;
201- try {
202- if (this .connectionTimeoutSeconds >0 ) {
203- int connectionTimeout =connectionTimeoutSeconds *1000 ;
204- RequestConfig requestConfig =
205- RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
206- .setConnectTimeout (connectionTimeout )
207- .setSocketTimeout (connectionTimeout ).build ();
208- ingestRequest .setConfig (requestConfig );
209- }
210- ingestRequest .setEntity (new StringEntity (objectMapper .writeValueAsString (request )));
211- ingestResponse =httpclient .execute (ingestRequest );
190+ try (final CloseableHttpResponse ingestResponse =httpclient .execute (ingestRequest )) {
212191if (ingestResponse !=null ) {
213192final int statusCode =ingestResponse .getStatusLine ().getStatusCode ();
214193if (200 !=statusCode ) {
@@ -220,22 +199,61 @@ public IngestAssetsResponse ingestKeys(IngestKeysRequest request, String merchan
220199 }
221200final HttpEntity responseEntity =ingestResponse .getEntity ();
222201if (responseEntity !=null ) {
223- final IngestAssetsResponse getPadResponse =objectMapper
224- .readValue (responseEntity .getContent (),IngestAssetsResponse .class );
225- return getPadResponse ;
202+ return objectMapper .readValue (responseEntity .getContent (),IngestAssetsResponse .class );
226203 }else {
227204throw new CastlabsException ("Empty response entity from Castlabs" );
228205 }
229206 }else {
230207throw new CastlabsException ("No response when ingesting keys into Castlabs" );
231208 }
232- }finally {
233- try {
234- if (ingestResponse !=null )
235- ingestResponse .close ();
236- }catch (IOException e ) {
237- // ignore
209+ }
210+ }
211+
212+ /**
213+ * Add a sub merchant account to Castlabs.
214+ *
215+ * @param request Request parameters to pass to Castlabs
216+ * @param merchantUuid UUID for the merchant that the sub-merchant is being created off
217+ * @return response from Castlabs
218+ * @throws CastlabsException error reported by Castlabs
219+ * @throws IOException network error while communicating with Castlabs REST API
220+ */
221+ public AddSubMerchantAccountResponse addSubMerchantAccount (final AddSubMerchantAccountRequest request ,final String merchantUuid )
222+ throws IOException ,CastlabsException {
223+
224+ final String uri =this .getUrlWithTicket (this .ingestionBaseUrl +"frontend/rest/reselling/v1/reseller/" +merchantUuid +"/submerchant/add" );
225+ final HttpPost addResellerRequest =new HttpPost (uri );
226+ addResellerRequest .addHeader ("Content-Type" ,"application/json" );
227+ addResellerRequest .setHeader ("Accept" ,"application/json" );
228+
229+ if (this .connectionTimeoutSeconds >0 ) {
230+ final int connectionTimeout =connectionTimeoutSeconds *1000 ;
231+ final RequestConfig requestConfig =
232+ RequestConfig .custom ().setConnectionRequestTimeout (connectionTimeout )
233+ .setConnectTimeout (connectionTimeout )
234+ .setSocketTimeout (connectionTimeout ).build ();
235+ addResellerRequest .setConfig (requestConfig );
236+ }
237+ addResellerRequest .setEntity (new StringEntity (objectMapper .writeValueAsString (request )));
238+
239+ final CloseableHttpClient httpclient =HttpClients .createDefault ();
240+ try (final CloseableHttpResponse httpResponse =httpclient .execute (addResellerRequest )){
241+ final HttpEntity responseEntity =httpResponse .getEntity ();
242+ if (responseEntity ==null ) {
243+ throw new CastlabsException ("Empty response entity from Castlabs. HTTP Status: " +httpResponse .getStatusLine ().getStatusCode ());
244+ }
245+
246+ final String responseBody =IOUtils .toString (responseEntity .getContent ());
247+ if (StringUtils .isBlank (responseBody )) {
248+ throw new CastlabsException ("Empty response entity from Castlabs. HTTP Status: " +httpResponse .getStatusLine ().getStatusCode ());
238249 }
250+
251+ final AddSubMerchantAccountResponse response =objectMapper .readValue (responseBody ,AddSubMerchantAccountResponse .class );
252+ if (response .getSubMerchantUuid () ==null ) {
253+ throw new CastlabsException ("Unexpected response from Castlabs: " +responseBody );
254+ }
255+ return response ;
239256 }
240257 }
258+
241259}