-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathJobClient.java
More file actions
320 lines (295 loc) · 11.2 KB
/
Copy pathJobClient.java
File metadata and controls
320 lines (295 loc) · 11.2 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
package com.modzy.sdk;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.ResponseProcessingException;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.modzy.sdk.dto.JobHistorySearchParams;
import com.modzy.sdk.exception.ApiException;
import com.modzy.sdk.model.Job;
import com.modzy.sdk.model.JobInput;
import com.modzy.sdk.model.JobInputStream;
import com.modzy.sdk.model.JobStatus;
import com.modzy.sdk.model.Model;
import com.modzy.sdk.model.ModelVersion;
import org.glassfish.jersey.media.multipart.MultiPart;
import com.modzy.sdk.util.LoggerFactory;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
/**
*
* Client of the Job API Services
*
*/
public class JobClient {
private static final String REST_PATH = "jobs";
private WebTarget restTarget;
private String apiKey;
private Logger logger;
public JobClient(WebTarget baseTarget, String apiKey) {
super();
this.logger = LoggerFactory.getLogger(this);
this.restTarget = baseTarget.path(JobClient.REST_PATH);
this.apiKey = apiKey;
}
/**
* Wrapper of the GenericType necesary for process the response of the API
*
* @return A GenericType object for parse a list of Job instances
*/
private GenericType<List<Job>> getListType(){
return new GenericType<List<Job>>() {};
}
/**
*
* Call the Modzy API Service and query on the history of jobs
*
* @param searchParams search parameters see {@link JobHistorySearchParams class}
* @return List of Jobs according to the search params
* @throws ApiException if there is something wrong with the service or the call
*/
public List<Job> getJobHistory(JobHistorySearchParams searchParams) throws ApiException{
WebTarget webTarget = this.restTarget.path("history");
ObjectMapper objMapper = new ObjectMapper();
Map<String, String> map = objMapper.convertValue(searchParams, new TypeReference<Map<String,String>>() {} );
for( Map.Entry<String,String> entry : map.entrySet() ) {
webTarget = webTarget.queryParam( entry.getKey(), entry.getValue() );
}
Builder builder = webTarget.request(MediaType.APPLICATION_JSON);
builder.header("Authorization", "ApiKey "+this.apiKey);
try {
logger.info("querying job history "+webTarget.getUri());
return builder.get(this.getListType());
}
catch(ResponseProcessingException rpe) {
this.logger.log(Level.SEVERE, rpe.getMessage(), rpe);
throw new ApiException(rpe);
}
catch(ProcessingException pr) {
this.logger.log(Level.SEVERE, pr.getMessage(), pr);
throw new ApiException(pr);
}
catch(WebApplicationException wae) {
this.logger.log(Level.SEVERE, wae.getMessage(), wae);
throw new ApiException(wae);
}
}
/**
*
* Call the Modzy API Service that post a new job and return it's instance
*
* @param job the job instance to pass to Modzy
* @return the updated instance of the Job returned by Modzy API
* @throws ApiException if there is something wrong with the service or the call
*/
public Job submitJob(Job job) throws ApiException{
if( job.getInput() != null && job.getInput() instanceof JobInputStream){
return submitOpenJob(job);
}
Builder builder = this.restTarget.request(MediaType.APPLICATION_JSON);
builder.header("Authorization", "ApiKey "+this.apiKey);
try {
logger.info("creating job: "+job);
job = builder.post(Entity.entity(job, MediaType.APPLICATION_JSON), Job.class);
job.setStatus( JobStatus.SUBMITTED );
return job;
} catch(ResponseProcessingException rpe) {
this.logger.log(Level.SEVERE, rpe.getMessage(), rpe);
throw new ApiException(rpe);
} catch(ProcessingException pr) {
this.logger.log(Level.SEVERE, pr.getMessage(), pr);
throw new ApiException(pr);
}
}
/**
* Call the Modzy API Services that post an open job and return it's final instance
*
* @param job
* @return
* @throws ApiException
*/
private Job submitOpenJob(Job job) throws ApiException{
//Open the job
Job openJob = this.submitJob(new Job(job.getModel()));
//Iterate and submit the inputs
try {
JobInput<InputStream> jobInput = (JobInput<InputStream>) job.getInput();
for (Map.Entry<String, Map<String, InputStream>> inputItem : jobInput.getSources().entrySet()) {
for (Map.Entry<String, InputStream> dataItem : inputItem.getValue().entrySet()) {
appendInput(openJob, inputItem.getKey(), dataItem.getKey(), dataItem.getValue());
}
}
} catch( ApiException ae ){
this.logger.log(Level.SEVERE, ae.getMessage(), ae);
try{
this.cancelJob(openJob);
} catch( ApiException e2 ){
this.logger.log(Level.WARNING, "Unpexpected exception trying to cancel the job", e2);
}
throw ae;
}
//Close the job
return this.closeJob(openJob);
}
private void appendInput(Job job, String inputItemKey, String dataItemKey, InputStream value) throws ApiException{
Builder builder = this.restTarget
.register(MultiPartFeature.class)
.path(job.getJobIdentifier()).path(inputItemKey).path(dataItemKey)
.request(MediaType.MULTIPART_FORM_DATA);
builder.header("Authorization", "ApiKey "+this.apiKey);
MultiPart data = new MultiPart();
data.bodyPart( new StreamDataBodyPart("input", value, dataItemKey) );
try {
logger.info("Adding input: "+job.getJobIdentifier()+" "+inputItemKey+" "+dataItemKey);
Response response = builder.post(Entity.entity(data, data.getMediaType()));
if( response.getStatus() >= 400 ){
throw new ApiException("The server respond with a status "+response.getStatus());
}
} catch(ResponseProcessingException rpe) {
this.logger.log(Level.SEVERE, rpe.getMessage(), rpe);
throw new ApiException(rpe);
} catch(ProcessingException pr) {
this.logger.log(Level.SEVERE, pr.getMessage(), pr);
throw new ApiException(pr);
}
}
private Job closeJob(Job job) throws ApiException{
Builder builder = this.restTarget.path(job.getJobIdentifier()).path("close").request(MediaType.APPLICATION_JSON);
builder.header("Authorization", "ApiKey "+this.apiKey);
try {
logger.info("closing job: "+job);
job = builder.post(Entity.entity(null, MediaType.APPLICATION_JSON), Job.class);
job.setStatus( JobStatus.SUBMITTED );
return job;
} catch(ResponseProcessingException rpe) {
this.logger.log(Level.SEVERE, rpe.getMessage(), rpe);
throw new ApiException(rpe);
} catch(ProcessingException pr) {
this.logger.log(Level.SEVERE, pr.getMessage(), pr);
throw new ApiException(pr);
}
}
/**
*
* Create a new Job for the model at specific version with the input provided
*
* @param model The model instance in which the model will run
* @param modelVersion The specific version of the model
* @param jobInput The inputs of the model to pass to Modzy
* @param explain If the model supports explainability, flag this job to return an explanation of the predictions
* @return the updated instance of the Job returned by Modzy API
* @throws ApiException if there is something wrong with the service or the call
*/
public Job submitJob(Model model, ModelVersion modelVersion, JobInput<?> jobInput, Boolean explain) throws ApiException{
return this.submitJob( new Job(model, modelVersion, jobInput) );
}
/**
*
* Create a new Job for the model at specific version with the input provided
*
* @param modelId identifier of the model
* @param modelVersionId identifier of the model version
* @param jobInput the inputs of the model to pass to Modzy
* @param explain If the model supports explainability, flag this job to return an explanation of the predictions
* @return the updated instance of the Job returned by Modzy API
* @throws ApiException if there is something wrong with the service or the call
*/
public Job submitJob(String modelId, String modelVersionId, JobInput<?> jobInput, Boolean explain) throws ApiException{
Model model = new Model();
model.setIdentifier(modelId);
ModelVersion modelVersion = new ModelVersion();
modelVersion.setVersion(modelVersionId);
return this.submitJob( new Job(model, modelVersion, jobInput, explain) );
}
/**
*
* Call the Modzy API Service that return a job instance by it's identifier
*
* @param jobId identifier of the job
* @return A Job instance if the jobId param is valid
* @throws ApiException if there is something wrong with the service or the call
*/
public Job getJob(String jobId) throws ApiException{
Builder builder = this.restTarget.path(jobId).request(MediaType.APPLICATION_JSON);
builder.header("Authorization", "ApiKey "+this.apiKey);
try {
return builder.get(Job.class);
}
catch(ResponseProcessingException rpe) {
this.logger.log(Level.SEVERE, rpe.getMessage(), rpe);
throw new ApiException(rpe);
}
catch(ProcessingException pr) {
this.logger.log(Level.SEVERE, pr.getMessage(), pr);
throw new ApiException(pr);
}
catch(WebApplicationException wae) {
this.logger.log(Level.SEVERE, wae.getMessage(), wae);
throw new ApiException(wae);
}
}
/**
*
* Refresh the state of the Job provided, this method is a shortcut to
* {@link JobClient#getJob(String) getJob(jobId)}
*
* @param job the Job instance that will be refreshed
* @return a updated job instance
* @throws ApiException if there is something wrong with the service or the call
*/
public Job getJob(Job job) throws ApiException{
return this.getJob(job.getJobIdentifier());
}
/**
*
* Call the Modzy API Service that cancel the Job by it's identifier
*
* @param jobId identifier of the Job
* @return a Job Instance if the jobId param is a valid identifier
* @throws ApiException if there is something wrong with the service or the call
*/
public Job cancelJob(String jobId) throws ApiException{
Builder builder = this.restTarget.path(jobId).request(MediaType.APPLICATION_JSON);
builder.header("Authorization", "ApiKey "+this.apiKey);
try {
logger.info("canceling job "+jobId);
return builder.delete(Job.class);
}
catch(ResponseProcessingException rpe) {
this.logger.log(Level.SEVERE, rpe.getMessage(), rpe);
throw new ApiException(rpe);
}
catch(ProcessingException pr) {
this.logger.log(Level.SEVERE, pr.getMessage(), pr);
throw new ApiException(pr);
}
catch(WebApplicationException wae) {
this.logger.log(Level.SEVERE, wae.getMessage(), wae);
throw new ApiException(wae);
}
}
/**
*
* Cancel the Job provided, this method is a shortcut to
* {@link JobClient#cancelJob(String) getJob(jobId)}
*
* @param job the Job instance that will be canceled
* @return a updated job instance (hopefully canceled)
* @throws ApiException if there is something wrong with the service or the call
*/
public Job cancelJob(Job job) throws ApiException{
this.logger.info("canceling job "+job);
return this.cancelJob(job.getJobIdentifier());
}
}