1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.series.remote;
23
24 import static java.lang.String.format;
25 import static javax.servlet.http.HttpServletResponse.SC_OK;
26 import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
27 import static javax.ws.rs.core.Response.Status.NOT_FOUND;
28 import static org.apache.commons.lang3.StringUtils.isBlank;
29 import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
30 import static org.apache.http.HttpStatus.SC_CREATED;
31 import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
32 import static org.apache.http.HttpStatus.SC_NOT_FOUND;
33 import static org.apache.http.HttpStatus.SC_NO_CONTENT;
34 import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
35
36 import org.opencastproject.metadata.dublincore.DublinCore;
37 import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
38 import org.opencastproject.metadata.dublincore.DublinCores;
39 import org.opencastproject.security.api.AccessControlList;
40 import org.opencastproject.security.api.AccessControlParser;
41 import org.opencastproject.security.api.TrustedHttpClient;
42 import org.opencastproject.security.api.UnauthorizedException;
43 import org.opencastproject.series.api.Series;
44 import org.opencastproject.series.api.SeriesException;
45 import org.opencastproject.series.api.SeriesService;
46 import org.opencastproject.serviceregistry.api.RemoteBase;
47 import org.opencastproject.serviceregistry.api.ServiceRegistry;
48 import org.opencastproject.util.NotFoundException;
49 import org.opencastproject.util.doc.rest.RestService;
50
51 import com.entwinemedia.fn.data.Opt;
52 import com.google.gson.Gson;
53 import com.google.gson.reflect.TypeToken;
54
55 import org.apache.commons.io.IOUtils;
56 import org.apache.http.HttpResponse;
57 import org.apache.http.NameValuePair;
58 import org.apache.http.client.entity.UrlEncodedFormEntity;
59 import org.apache.http.client.methods.HttpDelete;
60 import org.apache.http.client.methods.HttpGet;
61 import org.apache.http.client.methods.HttpPost;
62 import org.apache.http.client.methods.HttpPut;
63 import org.apache.http.client.utils.URLEncodedUtils;
64 import org.apache.http.entity.ByteArrayEntity;
65 import org.apache.http.entity.ContentType;
66 import org.apache.http.message.BasicNameValuePair;
67 import org.codehaus.jettison.json.JSONArray;
68 import org.codehaus.jettison.json.JSONObject;
69 import org.json.simple.parser.JSONParser;
70 import org.osgi.service.component.annotations.Component;
71 import org.osgi.service.component.annotations.Reference;
72 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76 import java.io.IOException;
77 import java.io.InputStreamReader;
78 import java.io.Reader;
79 import java.io.StringWriter;
80 import java.lang.reflect.Type;
81 import java.nio.charset.StandardCharsets;
82 import java.util.ArrayList;
83 import java.util.Date;
84 import java.util.HashMap;
85 import java.util.List;
86 import java.util.Map;
87 import java.util.Optional;
88 import java.util.TreeMap;
89
90 import javax.ws.rs.GET;
91 import javax.ws.rs.Path;
92 import javax.ws.rs.PathParam;
93 import javax.ws.rs.Produces;
94 import javax.ws.rs.WebApplicationException;
95 import javax.ws.rs.core.MediaType;
96 import javax.ws.rs.core.Response;
97
98
99
100
101 @Path("/series")
102 @RestService(
103 name = "seriesservice",
104 title = "Series Service Remote",
105 abstractText = "This service creates, edits and retrieves and helps managing series.",
106 notes = {
107 "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
108 "If the service is down or not working it will return a status 503, this means the the "
109 + "underlying service is not working and is either restarting or has failed",
110 "A status code 500 means a general failure has occurred which is not recoverable and was "
111 + "not anticipated. In other words, there is a bug! You should file an error report "
112 + "with your server logs from the time when the error occurred: "
113 + "<a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>"
114 }
115 )
116 @Component(
117 property = {
118 "service.description=Series Remote Service Proxy",
119 "opencast.service.type=org.opencastproject.series",
120 "opencast.service.path=/series",
121 "opencast.service.publish=false"
122 },
123 immediate = true,
124 service = { SeriesService.class, SeriesServiceRemoteImpl.class }
125 )
126 @JaxrsResource
127 public class SeriesServiceRemoteImpl extends RemoteBase implements SeriesService {
128
129 private static final Logger logger = LoggerFactory.getLogger(SeriesServiceRemoteImpl.class);
130
131
132 private static final Gson gson = new Gson();
133 private static final Type seriesListType = new TypeToken<ArrayList<Series>>() { }.getType();
134
135 public SeriesServiceRemoteImpl() {
136 super(JOB_TYPE);
137 }
138
139
140
141
142
143
144 @Override
145 @Reference
146 public void setTrustedHttpClient(TrustedHttpClient client) {
147 super.setTrustedHttpClient(client);
148 }
149
150
151
152
153
154
155 @Override
156 @Reference
157 public void setRemoteServiceManager(ServiceRegistry remoteServiceManager) {
158 super.setRemoteServiceManager(remoteServiceManager);
159 }
160
161 @Override
162 public DublinCoreCatalog updateSeries(DublinCoreCatalog dc) throws SeriesException, UnauthorizedException {
163 String seriesId = dc.getFirst(DublinCore.PROPERTY_IDENTIFIER);
164
165 HttpPost post = new HttpPost("/");
166 try {
167 List<BasicNameValuePair> params = new ArrayList<>();
168 params.add(new BasicNameValuePair("series", dc.toXmlString()));
169 post.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
170 } catch (Exception e) {
171 throw new SeriesException("Unable to assemble a remote series request for updating series " + seriesId, e);
172 }
173
174 HttpResponse response = getResponse(post, SC_NO_CONTENT, SC_CREATED, SC_UNAUTHORIZED);
175 try {
176 if (response != null) {
177 int statusCode = response.getStatusLine().getStatusCode();
178 if (SC_NO_CONTENT == statusCode) {
179 logger.info("Successfully updated series {} in the series service", seriesId);
180 return null;
181 } else if (SC_UNAUTHORIZED == statusCode) {
182 throw new UnauthorizedException("Not authorized to update series " + seriesId);
183 } else if (SC_CREATED == statusCode) {
184 DublinCoreCatalog catalogImpl = DublinCores.read(response.getEntity().getContent());
185 logger.info("Successfully created series {} in the series service", seriesId);
186 return catalogImpl;
187 }
188 }
189 } catch (UnauthorizedException e) {
190 throw e;
191 } catch (Exception e) {
192 throw new SeriesException("Unable to update series " + seriesId + " using the remote series services: " + e);
193 } finally {
194 closeConnection(response);
195 }
196 throw new SeriesException("Unable to update series " + seriesId + " using the remote series services");
197 }
198
199 @Override
200 public boolean updateAccessControl(String seriesID, AccessControlList accessControl)
201 throws NotFoundException, SeriesException, UnauthorizedException {
202 return updateAccessControl(seriesID, accessControl, false);
203 }
204
205 @Override
206 public boolean updateAccessControl(String seriesID, AccessControlList accessControl, boolean overrideEpisodeAcl)
207 throws NotFoundException, SeriesException, UnauthorizedException {
208 HttpPost post = new HttpPost(seriesID + "/accesscontrol");
209 try {
210 List<BasicNameValuePair> params = new ArrayList<>();
211 params.add(new BasicNameValuePair("seriesID", seriesID));
212 params.add(new BasicNameValuePair("acl", AccessControlParser.toXml(accessControl)));
213 params.add(new BasicNameValuePair("overrideEpisodeAcl", Boolean.toString(overrideEpisodeAcl)));
214 post.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
215 } catch (Exception e) {
216 throw new SeriesException("Unable to assemble a remote series request for updating an ACL " + accessControl, e);
217 }
218
219 HttpResponse response = getResponse(post, SC_NO_CONTENT, SC_CREATED, SC_NOT_FOUND, SC_UNAUTHORIZED);
220 try {
221 if (response != null) {
222 int status = response.getStatusLine().getStatusCode();
223 if (SC_NOT_FOUND == status) {
224 throw new NotFoundException("Series not found: " + seriesID);
225 } else if (SC_NO_CONTENT == status) {
226 logger.info("Successfully updated ACL of {} to the series service", seriesID);
227 return true;
228 } else if (SC_UNAUTHORIZED == status) {
229 throw new UnauthorizedException("Not authorized to update series ACL of " + seriesID);
230 } else if (SC_CREATED == status) {
231 logger.info("Successfully created ACL of {} to the series service", seriesID);
232 return false;
233 }
234 }
235 } finally {
236 closeConnection(response);
237 }
238 throw new SeriesException("Unable to update series ACL " + accessControl + " using the remote series services");
239 }
240
241 @Override
242 public void deleteSeries(String seriesID) throws SeriesException, NotFoundException, UnauthorizedException {
243 HttpDelete del = new HttpDelete(seriesID);
244 HttpResponse response = getResponse(del, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
245 try {
246 if (response != null) {
247 int statusCode = response.getStatusLine().getStatusCode();
248 if (SC_NOT_FOUND == statusCode) {
249 throw new NotFoundException("Series not found: " + seriesID);
250 } else if (SC_UNAUTHORIZED == statusCode) {
251 throw new UnauthorizedException("Not authorized to delete series " + seriesID);
252 } else if (SC_OK == statusCode) {
253 logger.info("Successfully deleted {} from the remote series index", seriesID);
254 return;
255 }
256 }
257 } finally {
258 closeConnection(response);
259 }
260 throw new SeriesException("Unable to remove " + seriesID + " from a remote series index");
261 }
262
263 @GET
264 @Produces(MediaType.APPLICATION_JSON)
265 @Path("{seriesID:.+}.json")
266 public Response getSeriesJSON(@PathParam("seriesID") String seriesID) throws UnauthorizedException {
267 logger.debug("Series Lookup: {}", seriesID);
268 try {
269 DublinCoreCatalog dc = getSeries(seriesID);
270 return Response.ok(dc.toJson()).build();
271 } catch (NotFoundException e) {
272 return Response.status(NOT_FOUND).build();
273 } catch (UnauthorizedException e) {
274 throw e;
275 } catch (Exception e) {
276 logger.error("Could not retrieve series: {}", e.getMessage());
277 throw new WebApplicationException(INTERNAL_SERVER_ERROR);
278 }
279 }
280
281 @GET
282 @Produces(MediaType.APPLICATION_JSON)
283 @Path("/{seriesID:.+}/acl.json")
284 public Response getSeriesAccessControlListJson(@PathParam("seriesID") String seriesID) {
285 logger.debug("Series ACL lookup: {}", seriesID);
286 try {
287 AccessControlList acl = getSeriesAccessControl(seriesID);
288 return Response.ok(acl).build();
289 } catch (NotFoundException e) {
290 return Response.status(NOT_FOUND).build();
291 } catch (SeriesException e) {
292 logger.error("Could not retrieve series ACL: {}", e.getMessage());
293 throw new WebApplicationException(INTERNAL_SERVER_ERROR);
294 }
295 }
296
297 @Override
298 public DublinCoreCatalog getSeries(String seriesID) throws SeriesException, NotFoundException, UnauthorizedException {
299 HttpGet get = new HttpGet(seriesID + ".xml");
300 HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
301 try {
302 if (response != null) {
303 if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
304 throw new NotFoundException("Series " + seriesID + " not found in remote series index!");
305 } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
306 throw new UnauthorizedException("Not authorized to get series " + seriesID);
307 } else {
308 DublinCoreCatalog dublinCoreCatalog = DublinCores.read(response.getEntity().getContent());
309 logger.debug("Successfully received series {} from the remote series index", seriesID);
310 return dublinCoreCatalog;
311 }
312 }
313 } catch (UnauthorizedException e) {
314 throw e;
315 } catch (NotFoundException e) {
316 throw e;
317 } catch (Exception e) {
318 throw new SeriesException("Unable to parse series from remote series index: " + e);
319 } finally {
320 closeConnection(response);
321 }
322 throw new SeriesException("Unable to get series from remote series index");
323 }
324
325 @Override
326 public List<Series> getAllForAdministrativeRead(Date from, Optional<Date> to, int limit)
327 throws SeriesException, UnauthorizedException {
328
329 StringBuilder url = new StringBuilder();
330 url.append("/allInRangeAdministrative.json?");
331
332 List<NameValuePair> queryParams = new ArrayList<>();
333 queryParams.add(new BasicNameValuePair("from", Long.toString(from.getTime())));
334 queryParams.add(new BasicNameValuePair("limit", Integer.toString(limit)));
335 if (to.isPresent()) {
336 queryParams.add(new BasicNameValuePair("to", Long.toString(to.get().getTime())));
337 }
338 url.append(URLEncodedUtils.format(queryParams, StandardCharsets.UTF_8));
339 HttpGet get = new HttpGet(url.toString());
340
341
342 HttpResponse response = getResponse(get, SC_OK, SC_BAD_REQUEST, SC_UNAUTHORIZED);
343 try {
344 if (response == null) {
345 throw new SeriesException("Unable to get series from remote series index");
346 }
347
348 if (response.getStatusLine().getStatusCode() == SC_BAD_REQUEST) {
349 throw new SeriesException("internal server error when fetching /allInRangeAdministrative.json");
350 } else if (response.getStatusLine().getStatusCode() == SC_UNAUTHORIZED) {
351 throw new UnauthorizedException("got UNAUTHORIZED when fetching /allInRangeAdministrative.json");
352 } else {
353
354 Reader reader = new InputStreamReader(response.getEntity().getContent(), "UTF-8");
355 return gson.fromJson(reader, seriesListType);
356 }
357 } catch (IOException e) {
358 throw new SeriesException("failed to reader response body of /allInRangeAdministrative.json", e);
359 } finally {
360 closeConnection(response);
361 }
362 }
363
364 @Override
365 public AccessControlList getSeriesAccessControl(String seriesID) throws NotFoundException, SeriesException {
366 HttpGet get = new HttpGet(seriesID + "/acl.xml");
367 HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND);
368 try {
369 if (response != null) {
370 if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
371 throw new NotFoundException("Series ACL " + seriesID + " not found on remote series index!");
372 } else {
373 AccessControlList acl = AccessControlParser.parseAcl(response.getEntity().getContent());
374 logger.info("Successfully get series ACL {} from the remote series index", seriesID);
375 return acl;
376 }
377 }
378 } catch (NotFoundException e) {
379 throw e;
380 } catch (Exception e) {
381 throw new SeriesException("Unable to parse series ACL form remote series index: " + e);
382 } finally {
383 closeConnection(response);
384 }
385 throw new SeriesException("Unable to get series ACL from remote series index");
386 }
387
388 @Override
389 public int getSeriesCount() throws SeriesException {
390 HttpGet get = new HttpGet("/count");
391 HttpResponse response = getResponse(get);
392 try {
393 if (response != null) {
394 int count = Integer.parseInt(IOUtils.toString(response.getEntity().getContent()));
395 logger.info("Successfully get series dublin core catalog list from the remote series index");
396 return count;
397 }
398 } catch (Exception e) {
399 throw new SeriesException("Unable to count series from remote series index: " + e);
400 } finally {
401 closeConnection(response);
402 }
403 throw new SeriesException("Unable to count series from remote series index");
404 }
405
406 @Override
407 public Map<String, String> getSeriesProperties(String seriesID)
408 throws SeriesException, NotFoundException, UnauthorizedException {
409 HttpGet get = new HttpGet(seriesID + "/properties.json");
410 HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
411 JSONParser parser = new JSONParser();
412 try {
413 if (response != null) {
414 if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
415 throw new NotFoundException("Series " + seriesID + " not found in remote series index!");
416 } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
417 throw new UnauthorizedException("Not authorized to get series " + seriesID);
418 } else {
419 logger.debug("Successfully received series {} properties from the remote series index", seriesID);
420 StringWriter writer = new StringWriter();
421 IOUtils.copy(response.getEntity().getContent(), writer, StandardCharsets.UTF_8);
422 JSONArray jsonProperties = (JSONArray) parser.parse(writer.toString());
423 Map<String, String> properties = new TreeMap<>();
424 for (int i = 0; i < jsonProperties.length(); i++) {
425 JSONObject property = (JSONObject) jsonProperties.get(i);
426 JSONArray names = property.names();
427 for (int j = 0; j < names.length(); j++) {
428 properties.put(names.get(j).toString(), property.get(names.get(j).toString()).toString());
429 }
430 }
431 return properties;
432 }
433 }
434 } catch (UnauthorizedException e) {
435 throw e;
436 } catch (NotFoundException e) {
437 throw e;
438 } catch (Exception e) {
439 throw new SeriesException("Unable to parse series properties from remote series index: " + e);
440 } finally {
441 closeConnection(response);
442 }
443 throw new SeriesException("Unable to get series from remote series index");
444 }
445
446 @Override
447 public String getSeriesProperty(String seriesID, String propertyName)
448 throws SeriesException, NotFoundException, UnauthorizedException {
449 HttpGet get = new HttpGet(seriesID + "/property/" + propertyName + ".json");
450 HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
451 try {
452 if (response != null) {
453 if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
454 throw new NotFoundException("Series " + seriesID + " not found in remote series index!");
455 } else if (SC_UNAUTHORIZED == response.getStatusLine().getStatusCode()) {
456 throw new UnauthorizedException("Not authorized to get series " + seriesID);
457 } else {
458 logger.debug("Successfully received series {} property {} from the remote series index", seriesID,
459 propertyName);
460 StringWriter writer = new StringWriter();
461 IOUtils.copy(response.getEntity().getContent(), writer, StandardCharsets.UTF_8);
462 return writer.toString();
463 }
464 }
465 } catch (UnauthorizedException e) {
466 throw e;
467 } catch (NotFoundException e) {
468 throw e;
469 } catch (Exception e) {
470 throw new SeriesException("Unable to parse series from remote series index: " + e);
471 } finally {
472 closeConnection(response);
473 }
474 throw new SeriesException("Unable to get series from remote series index");
475 }
476
477 @Override
478 public void updateSeriesProperty(String seriesID, String propertyName, String propertyValue)
479 throws SeriesException, NotFoundException, UnauthorizedException {
480 HttpPost post = new HttpPost("/" + seriesID + "/property");
481 try {
482 List<BasicNameValuePair> params = new ArrayList<>();
483 params.add(new BasicNameValuePair("name", propertyName));
484 params.add(new BasicNameValuePair("value", propertyValue));
485 post.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
486 } catch (Exception e) {
487 throw new SeriesException("Unable to assemble a remote series request for updating series " + seriesID
488 + " series property " + propertyName + ":" + propertyValue, e);
489 }
490
491 HttpResponse response = getResponse(post, SC_NO_CONTENT, SC_CREATED, SC_UNAUTHORIZED);
492 try {
493 if (response != null) {
494 int statusCode = response.getStatusLine().getStatusCode();
495 if (SC_NO_CONTENT == statusCode) {
496 logger.info("Successfully updated series {} with property name {} and value {} in the series service",
497 seriesID, propertyName, propertyValue);
498 return;
499 } else if (SC_UNAUTHORIZED == statusCode) {
500 throw new UnauthorizedException("Not authorized to update series " + seriesID);
501 }
502 }
503 } catch (UnauthorizedException e) {
504 throw e;
505 } catch (Exception e) {
506 throw new SeriesException("Unable to update series " + seriesID + " with property " + propertyName + ":"
507 + propertyValue + " using the remote series services: ", e);
508 } finally {
509 closeConnection(response);
510 }
511 throw new SeriesException("Unable to update series " + seriesID + " using the remote series services");
512 }
513
514 @Override
515 public void deleteSeriesProperty(String seriesID, String propertyName)
516 throws SeriesException, NotFoundException, UnauthorizedException {
517 HttpDelete del = new HttpDelete("/" + seriesID + "/property/" + propertyName);
518 HttpResponse response = getResponse(del, SC_OK, SC_NOT_FOUND, SC_UNAUTHORIZED);
519 try {
520 if (response != null) {
521 int statusCode = response.getStatusLine().getStatusCode();
522 if (SC_NOT_FOUND == statusCode) {
523 throw new NotFoundException("Series not found: " + seriesID);
524 } else if (SC_UNAUTHORIZED == statusCode) {
525 throw new UnauthorizedException("Not authorized to delete series " + seriesID);
526 } else if (SC_OK == statusCode) {
527 logger.info("Successfully deleted {} from the remote series index", seriesID);
528 return;
529 }
530 }
531 } finally {
532 closeConnection(response);
533 }
534 throw new SeriesException("Unable to remove " + seriesID + " from a remote series index");
535 }
536
537 @Override
538 public boolean updateExtendedMetadata(String seriesId, String type, DublinCoreCatalog dc) throws SeriesException {
539 HttpPut put = new HttpPut("/" + seriesId + "/extendedMetadata/" + type);
540 try {
541 List<BasicNameValuePair> params = new ArrayList<>();
542 params.add(new BasicNameValuePair("dc", dc.toXmlString()));
543 put.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
544 } catch (Exception e) {
545 throw new SeriesException("Unable to assemble a remote series request for updating extended metadata of series "
546 + seriesId, e);
547 }
548
549 HttpResponse response = getResponse(put, SC_NO_CONTENT, SC_CREATED, SC_INTERNAL_SERVER_ERROR);
550 try {
551 if (response == null) {
552 throw new SeriesException(format("Error while updating extended metadata catalog of type '%s' for series '%s'",
553 type, seriesId));
554 } else {
555 final int statusCode = response.getStatusLine().getStatusCode();
556 switch (statusCode) {
557 case SC_NO_CONTENT:
558 case SC_CREATED:
559 return true;
560 case SC_INTERNAL_SERVER_ERROR:
561 throw new SeriesException(
562 format("Error while updating extended metadata catalog of type '%s' for series '%s'", type,
563 seriesId));
564 default:
565 throw new SeriesException(format("Unexpected status code", statusCode));
566 }
567 }
568 } finally {
569 closeConnection(response);
570 }
571 }
572
573 @Override
574 public Opt<Map<String, byte[]>> getSeriesElements(String seriesID) throws SeriesException {
575 HttpGet get = new HttpGet("/" + seriesID + "/elements.json");
576 HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_INTERNAL_SERVER_ERROR);
577 JSONParser parser = new JSONParser();
578
579 try {
580 if (response == null) {
581 throw new SeriesException(format("Error while retrieving elements from series '%s'", seriesID));
582 } else {
583 final int statusCode = response.getStatusLine().getStatusCode();
584 switch (statusCode) {
585 case SC_OK:
586 JSONArray elementArray = (JSONArray) parser.parse(IOUtils.toString(response.getEntity().getContent()));
587 Map<String, byte[]> elements = new HashMap<>();
588 for (int i = 0; i < elementArray.length(); i++) {
589 final String type = elementArray.getString(i);
590 Opt<byte[]> optData = getSeriesElementData(seriesID, type);
591 if (optData.isSome()) {
592 elements.put(type, optData.get());
593 } else {
594 throw new SeriesException(format("Tried to load non-existing element of type '%s'", type));
595 }
596 }
597 return Opt.some(elements);
598 case SC_NOT_FOUND:
599 return Opt.none();
600 case SC_INTERNAL_SERVER_ERROR:
601 throw new SeriesException(format("Error while retrieving elements from series '%s'", seriesID));
602 default:
603 throw new SeriesException(format("Unexpected status code", statusCode));
604 }
605 }
606 } catch (Exception e) {
607 logger.warn("Error while retrieving elements from remote service:", e);
608 throw new SeriesException(e);
609 } finally {
610 closeConnection(response);
611 }
612 }
613
614 @Override
615 public Opt<byte[]> getSeriesElementData(String seriesID, String type) throws SeriesException {
616 HttpGet get = new HttpGet("/" + seriesID + "/elements/" + type);
617 HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND, SC_INTERNAL_SERVER_ERROR);
618
619 try {
620 if (response == null) {
621 throw new SeriesException(
622 format("Error while retrieving element of type '%s' from series '%s'", type, seriesID));
623 } else {
624 final int statusCode = response.getStatusLine().getStatusCode();
625 switch (statusCode) {
626 case SC_OK:
627 return Opt.some(IOUtils.toByteArray(response.getEntity().getContent()));
628 case SC_NOT_FOUND:
629 return Opt.none();
630 case SC_INTERNAL_SERVER_ERROR:
631 throw new SeriesException(
632 format("Error while retrieving element of type '%s' from series '%s'", type, seriesID));
633 default:
634 throw new SeriesException(format("Unexpected status code", statusCode));
635 }
636 }
637 } catch (Exception e) {
638 logger.warn("Error while retrieving element from remote service:", e);
639 throw new SeriesException(e);
640 } finally {
641 closeConnection(response);
642 }
643 }
644
645 @Override
646 public boolean updateSeriesElement(String seriesID, String type, byte[] data) throws SeriesException {
647 HttpPut put = new HttpPut("/" + seriesID + "/elements/" + type);
648 put.setEntity(new ByteArrayEntity(data, ContentType.DEFAULT_BINARY));
649
650 HttpResponse response = getResponse(put, SC_CREATED, SC_NO_CONTENT, SC_INTERNAL_SERVER_ERROR);
651 try {
652 if (response == null) {
653 throw new SeriesException(format("Error while updating element of type '%s' in series '%s'", type, seriesID));
654 } else {
655 final int statusCode = response.getStatusLine().getStatusCode();
656 switch (statusCode) {
657 case SC_NO_CONTENT:
658 case SC_CREATED:
659 return true;
660 case SC_INTERNAL_SERVER_ERROR:
661 throw new SeriesException(
662 format("Error while updating element of type '%s' in series '%s'", type, seriesID));
663 default:
664 throw new SeriesException(format("Unexpected status code", statusCode));
665 }
666 }
667 } finally {
668 closeConnection(response);
669 }
670 }
671
672 @Override
673 public boolean deleteSeriesElement(String seriesID, String type) throws SeriesException {
674 if (isBlank(seriesID)) {
675 throw new IllegalArgumentException("Series ID must not be blank");
676 }
677 if (isBlank(type)) {
678 throw new IllegalArgumentException("Element type must not be blank");
679 }
680
681 HttpDelete del = new HttpDelete("/" + seriesID + "/elements/" + type);
682 HttpResponse response = getResponse(del, SC_NO_CONTENT, SC_NOT_FOUND, SC_INTERNAL_SERVER_ERROR);
683 try {
684 if (response == null) {
685 throw new SeriesException("Unable to remove " + seriesID + " from a remote series index");
686 } else {
687 final int statusCode = response.getStatusLine().getStatusCode();
688 switch (statusCode) {
689 case SC_NO_CONTENT:
690 return true;
691 case SC_NOT_FOUND:
692 return false;
693 case SC_INTERNAL_SERVER_ERROR:
694 throw new SeriesException(
695 format("Error while deleting element of type '%s' from series '%s'", type, seriesID));
696 default:
697 throw new SeriesException(format("Unexpected status code", statusCode));
698 }
699 }
700 } finally {
701 closeConnection(response);
702 }
703 }
704 }