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.adminui.endpoint;
23
24 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
25 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
26 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
27 import static javax.servlet.http.HttpServletResponse.SC_OK;
28 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
29 import static org.apache.commons.lang3.StringUtils.isNotBlank;
30 import static org.apache.commons.lang3.StringUtils.trimToNull;
31 import static org.opencastproject.index.service.util.JSONUtils.safeString;
32 import static org.opencastproject.index.service.util.RestUtils.notFound;
33 import static org.opencastproject.index.service.util.RestUtils.okJson;
34 import static org.opencastproject.index.service.util.RestUtils.okJsonList;
35 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
36
37 import org.opencastproject.elasticsearch.api.SearchIndexException;
38 import org.opencastproject.elasticsearch.api.SearchResult;
39 import org.opencastproject.elasticsearch.api.SearchResultItem;
40 import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
41 import org.opencastproject.elasticsearch.index.objects.series.Series;
42 import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
43 import org.opencastproject.index.service.resources.list.query.ThemesListQuery;
44 import org.opencastproject.index.service.util.RestUtils;
45 import org.opencastproject.security.api.SecurityService;
46 import org.opencastproject.security.api.UnauthorizedException;
47 import org.opencastproject.security.api.User;
48 import org.opencastproject.series.api.SeriesException;
49 import org.opencastproject.series.api.SeriesService;
50 import org.opencastproject.staticfiles.api.StaticFileService;
51 import org.opencastproject.staticfiles.endpoint.StaticFileRestService;
52 import org.opencastproject.themes.Theme;
53 import org.opencastproject.themes.ThemesServiceDatabase;
54 import org.opencastproject.themes.persistence.ThemesServiceDatabaseException;
55 import org.opencastproject.util.DateTimeSupport;
56 import org.opencastproject.util.EqualsUtil;
57 import org.opencastproject.util.NotFoundException;
58 import org.opencastproject.util.RestUtil;
59 import org.opencastproject.util.RestUtil.R;
60 import org.opencastproject.util.doc.rest.RestParameter;
61 import org.opencastproject.util.doc.rest.RestParameter.Type;
62 import org.opencastproject.util.doc.rest.RestQuery;
63 import org.opencastproject.util.doc.rest.RestResponse;
64 import org.opencastproject.util.doc.rest.RestService;
65 import org.opencastproject.util.requests.SortCriterion;
66
67 import com.google.gson.JsonArray;
68 import com.google.gson.JsonObject;
69
70 import org.apache.commons.lang3.BooleanUtils;
71 import org.apache.commons.lang3.StringUtils;
72 import org.osgi.framework.BundleContext;
73 import org.osgi.service.component.annotations.Activate;
74 import org.osgi.service.component.annotations.Component;
75 import org.osgi.service.component.annotations.Reference;
76 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 import java.io.IOException;
81 import java.util.ArrayList;
82 import java.util.Date;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Optional;
86
87 import javax.servlet.http.HttpServletResponse;
88 import javax.ws.rs.DELETE;
89 import javax.ws.rs.FormParam;
90 import javax.ws.rs.GET;
91 import javax.ws.rs.POST;
92 import javax.ws.rs.PUT;
93 import javax.ws.rs.Path;
94 import javax.ws.rs.PathParam;
95 import javax.ws.rs.Produces;
96 import javax.ws.rs.QueryParam;
97 import javax.ws.rs.WebApplicationException;
98 import javax.ws.rs.core.MediaType;
99 import javax.ws.rs.core.Response;
100 import javax.ws.rs.core.Response.Status;
101
102 @Path("/admin-ng/themes")
103 @RestService(
104 name = "themes",
105 title = "Themes facade service",
106 abstractText = "Provides operations for the themes",
107 notes = { "This service offers the default themes CRUD Operations for the admin UI.",
108 "<strong>Important:</strong> "
109 + "<em>This service is for exclusive use by the module admin-ui. Its API might change "
110 + "anytime without prior notice. Any dependencies other than the admin UI will be strictly ignored. "
111 + "DO NOT use this for integration of third-party applications.<em>"})
112 @Component(
113 immediate = true,
114 service = ThemesEndpoint.class,
115 property = {
116 "service.description=Admin UI - Themes Endpoint",
117 "opencast.service.type=org.opencastproject.adminui.ThemesEndpoint",
118 "opencast.service.path=/admin-ng/themes",
119 }
120 )
121 @JaxrsResource
122 public class ThemesEndpoint {
123
124
125 private static final Logger logger = LoggerFactory.getLogger(ThemesEndpoint.class);
126
127
128 private ThemesServiceDatabase themesServiceDatabase;
129
130
131 private SecurityService securityService;
132
133
134 private ElasticsearchIndex searchIndex;
135
136
137 private SeriesService seriesService;
138
139
140 private StaticFileService staticFileService;
141
142
143 private StaticFileRestService staticFileRestService;
144
145
146 @Reference
147 public void setThemesServiceDatabase(ThemesServiceDatabase themesServiceDatabase) {
148 this.themesServiceDatabase = themesServiceDatabase;
149 }
150
151
152 @Reference
153 public void setSecurityService(SecurityService securityService) {
154 this.securityService = securityService;
155 }
156
157
158 @Reference
159 public void setIndex(ElasticsearchIndex index) {
160 this.searchIndex = index;
161 }
162
163
164 @Reference
165 public void setSeriesService(SeriesService seriesService) {
166 this.seriesService = seriesService;
167 }
168
169
170 @Reference
171 public void setStaticFileService(StaticFileService staticFileService) {
172 this.staticFileService = staticFileService;
173 }
174
175
176 @Reference
177 public void setStaticFileRestService(StaticFileRestService staticFileRestService) {
178 this.staticFileRestService = staticFileRestService;
179 }
180
181 @Activate
182 public void activate(BundleContext bundleContext) {
183 logger.info("Activate themes endpoint");
184 }
185
186 @GET
187 @Produces({ MediaType.APPLICATION_JSON })
188 @Path("themes.json")
189 @RestQuery(
190 name = "getThemes",
191 description = "Return all of the known themes on the system",
192 restParameters = {
193 @RestParameter(name = "filter", isRequired = false, description = "The filter used for the query. They "
194 + "should be formated like that: 'filter1:value1,filter2:value2'", type = STRING),
195 @RestParameter(defaultValue = "0", description = "The maximum number of items to return per page.",
196 isRequired = false, name = "limit", type = RestParameter.Type.INTEGER),
197 @RestParameter(defaultValue = "0", description = "The page number.", isRequired = false, name = "offset",
198 type = RestParameter.Type.INTEGER),
199 @RestParameter(name = "sort", isRequired = false, description = "The sort order. May include any of the "
200 + "following: NAME, CREATOR. Add '_DESC' to reverse the sort order (e.g. CREATOR_DESC).", type = STRING)
201 },
202 responses = {
203 @RestResponse(description = "A JSON representation of the themes", responseCode = HttpServletResponse.SC_OK)
204 },
205 returnDescription = "")
206 public Response getThemes(@QueryParam("filter") String filter, @QueryParam("limit") int limit,
207 @QueryParam("offset") int offset, @QueryParam("sort") String sort) {
208 Optional<Integer> optLimit = Optional.ofNullable(limit);
209 Optional<Integer> optOffset = Optional.ofNullable(offset);
210 Optional<String> optSort = Optional.ofNullable(trimToNull(sort));
211
212 Map<String, String> filters = RestUtils.parseFilter(filter);
213
214 ArrayList<SortCriterion> sortCriteria;
215 if (optSort.isPresent()) {
216 sortCriteria = RestUtils.parseSortQueryParameter(optSort.get());
217 } else {
218 sortCriteria = new ArrayList<>();
219 }
220 Optional<String> optCreatorFilter = Optional.ofNullable(filters.get(ThemesListQuery.FILTER_CREATOR_NAME));
221 Optional<String> optTextFilter = Optional.ofNullable(filters.get(ThemesListQuery.FILTER_TEXT_NAME));
222
223 List<Theme> results = null;
224 int total;
225 try {
226 results = themesServiceDatabase.findThemes(
227 optLimit,
228 optOffset,
229 sortCriteria,
230 optCreatorFilter,
231 optTextFilter
232 );
233 total = themesServiceDatabase.countThemes();
234 } catch (Exception e) {
235 logger.error("Could not get themes from the database:", e);
236 return RestUtil.R.serverError();
237 }
238
239
240 List<JsonObject> themesJSON = new ArrayList<>();
241 for (Theme theme : results) {
242 themesJSON.add(themeToJSON(theme, false));
243 }
244
245 return okJsonList(
246 themesJSON,
247 Optional.ofNullable(offset).orElse(0),
248 Optional.ofNullable(limit).orElse(0),
249 total
250 );
251 }
252
253 @GET
254 @Path("{themeId}.json")
255 @Produces(MediaType.APPLICATION_JSON)
256 @RestQuery(
257 name = "getTheme",
258 description = "Returns the theme by the given id as JSON",
259 returnDescription = "The theme as JSON",
260 pathParameters = {
261 @RestParameter(name = "themeId", description = "The theme id", isRequired = true,
262 type = RestParameter.Type.INTEGER)
263 },
264 responses = {
265 @RestResponse(description = "Returns the theme as JSON", responseCode = HttpServletResponse.SC_OK),
266 @RestResponse(description = "No theme with this identifier was found.",
267 responseCode = HttpServletResponse.SC_NOT_FOUND)
268 })
269 public Response getThemeResponse(@PathParam("themeId") long id) {
270 try {
271 Theme theme = themesServiceDatabase.getTheme(id);
272
273 return okJson(themeToJSON(theme, true));
274 } catch (ThemesServiceDatabaseException e) {
275 return RestUtil.R.serverError();
276 } catch (NotFoundException e) {
277 return notFound("Cannot find a theme with id '%s'", id);
278 }
279 }
280
281 @GET
282 @Path("{themeId}/usage.json")
283 @Produces(MediaType.APPLICATION_JSON)
284 @RestQuery(
285 name = "getThemeUsage",
286 description = "Returns the theme usage by the given id as JSON",
287 returnDescription = "The theme usage as JSON",
288 pathParameters = {
289 @RestParameter(name = "themeId", description = "The theme id", isRequired = true,
290 type = RestParameter.Type.INTEGER)
291 },
292 responses = {
293 @RestResponse(description = "Returns the theme usage as JSON", responseCode = HttpServletResponse.SC_OK),
294 @RestResponse(description = "Theme with the given id does not exist",
295 responseCode = HttpServletResponse.SC_NOT_FOUND)
296 })
297 public Response getThemeUsage(@PathParam("themeId") long themeId) {
298 try {
299 themesServiceDatabase.getTheme(themeId);
300
301 SeriesSearchQuery query = new SeriesSearchQuery(securityService.getOrganization().getId(),
302 securityService.getUser()).withTheme(themeId);
303 SearchResult<Series> results = null;
304 try {
305 results = searchIndex.getByQuery(query);
306 } catch (SearchIndexException e) {
307 logger.error("The admin UI Search Index was not able to get the series with theme '{}':", themeId,
308 e);
309 return RestUtil.R.serverError();
310 }
311 JsonArray seriesValues = new JsonArray();
312 for (SearchResultItem<Series> item : results.getItems()) {
313 Series series = item.getSource();
314 JsonObject seriesJson = new JsonObject();
315 seriesJson.addProperty("id", series.getIdentifier());
316 seriesJson.addProperty("title", series.getTitle());
317 seriesValues.add(seriesJson);
318 }
319 JsonObject responseJson = new JsonObject();
320 responseJson.add("series", seriesValues);
321
322 return okJson(responseJson);
323 } catch (ThemesServiceDatabaseException e) {
324 return RestUtil.R.serverError();
325 } catch (NotFoundException e) {
326 return notFound("Cannot find a theme with id {}", themeId);
327 }
328 }
329
330 @POST
331 @Path("")
332 @RestQuery(
333 name = "createTheme",
334 description = "Add a theme",
335 returnDescription = "Return the created theme",
336 restParameters = {
337 @RestParameter(name = "default", description = "Whether the theme is default",
338 isRequired = true, type = Type.BOOLEAN),
339 @RestParameter(name = "name", description = "The theme name",
340 isRequired = true, type = Type.STRING),
341 @RestParameter(name = "description", description = "The theme description",
342 isRequired = false, type = Type.TEXT),
343 @RestParameter(name = "bumperActive", description = "Whether the theme bumper is active",
344 isRequired = false, type = Type.BOOLEAN),
345 @RestParameter(name = "trailerActive", description = "Whether the theme trailer is active",
346 isRequired = false, type = Type.BOOLEAN),
347 @RestParameter(name = "titleSlideActive", description = "Whether the theme title slide is active",
348 isRequired = false, type = Type.BOOLEAN),
349 @RestParameter(name = "licenseSlideActive", description = "Whether the theme license slide is active",
350 isRequired = false, type = Type.BOOLEAN),
351 @RestParameter(name = "watermarkActive", description = "Whether the theme watermark is active",
352 isRequired = false, type = Type.BOOLEAN),
353 @RestParameter(name = "bumperFile", description = "The theme bumper file",
354 isRequired = false, type = Type.STRING),
355 @RestParameter(name = "trailerFile", description = "The theme trailer file",
356 isRequired = false, type = Type.STRING),
357 @RestParameter(name = "watermarkFile", description = "The theme watermark file",
358 isRequired = false, type = Type.STRING),
359 @RestParameter(name = "titleSlideBackground", description = "The theme title slide background file",
360 isRequired = false, type = Type.STRING),
361 @RestParameter(name = "licenseSlideBackground", description = "The theme license slide background file",
362 isRequired = false, type = Type.STRING),
363 @RestParameter(name = "titleSlideMetadata", description = "The theme title slide metadata",
364 isRequired = false, type = Type.STRING),
365 @RestParameter(name = "licenseSlideDescription", description = "The theme license slide description",
366 isRequired = false, type = Type.STRING),
367 @RestParameter(name = "watermarkPosition", description = "The theme watermark position",
368 isRequired = false, type = Type.STRING),
369 },
370 responses = {
371 @RestResponse(responseCode = SC_OK, description = "Theme created"),
372 @RestResponse(responseCode = SC_BAD_REQUEST, description = "The theme references a non-existing file")
373 })
374 public Response createTheme(@FormParam("default") boolean isDefault, @FormParam("name") String name,
375 @FormParam("description") String description, @FormParam("bumperActive") Boolean bumperActive,
376 @FormParam("trailerActive") Boolean trailerActive, @FormParam("titleSlideActive") Boolean titleSlideActive,
377 @FormParam("licenseSlideActive") Boolean licenseSlideActive,
378 @FormParam("watermarkActive") Boolean watermarkActive, @FormParam("bumperFile") String bumperFile,
379 @FormParam("trailerFile") String trailerFile, @FormParam("watermarkFile") String watermarkFile,
380 @FormParam("titleSlideBackground") String titleSlideBackground,
381 @FormParam("licenseSlideBackground") String licenseSlideBackground,
382 @FormParam("titleSlideMetadata") String titleSlideMetadata,
383 @FormParam("licenseSlideDescription") String licenseSlideDescription,
384 @FormParam("watermarkPosition") String watermarkPosition) {
385 User creator = securityService.getUser();
386
387 Theme theme = new Theme(Optional.<Long> empty(), new Date(), isDefault, creator, name,
388 StringUtils.trimToNull(description), BooleanUtils.toBoolean(bumperActive),
389 StringUtils.trimToNull(bumperFile), BooleanUtils.toBoolean(trailerActive),
390 StringUtils.trimToNull(trailerFile), BooleanUtils.toBoolean(titleSlideActive),
391 StringUtils.trimToNull(titleSlideMetadata), StringUtils.trimToNull(titleSlideBackground),
392 BooleanUtils.toBoolean(licenseSlideActive), StringUtils.trimToNull(licenseSlideBackground),
393 StringUtils.trimToNull(licenseSlideDescription), BooleanUtils.toBoolean(watermarkActive),
394 StringUtils.trimToNull(watermarkFile), StringUtils.trimToNull(watermarkPosition));
395
396 try {
397 persistReferencedFiles(theme);
398 } catch (NotFoundException e) {
399 logger.warn("A file that is referenced in theme '{}' was not found: {}", theme, e.getMessage());
400 return R.badRequest("Referenced non-existing file");
401 } catch (IOException e) {
402 logger.warn("Error while persisting file: {}", e.getMessage());
403 return R.serverError();
404 }
405
406 try {
407 Theme createdTheme = themesServiceDatabase.updateTheme(theme);
408 return RestUtils.okJson(themeToJSON(createdTheme,false));
409 } catch (ThemesServiceDatabaseException e) {
410 logger.error("Unable to create a theme");
411 return RestUtil.R.serverError();
412 }
413 }
414
415 @PUT
416 @Path("{themeId}")
417 @RestQuery(
418 name = "updateTheme",
419 description = "Updates a theme",
420 returnDescription = "Return the updated theme",
421 pathParameters = {
422 @RestParameter(name = "themeId", description = "The theme identifier", isRequired = true, type = Type.INTEGER)
423 },
424 restParameters = {
425 @RestParameter(name = "default", description = "Whether the theme is default",
426 isRequired = false, type = Type.BOOLEAN),
427 @RestParameter(name = "name", description = "The theme name",
428 isRequired = false, type = Type.STRING),
429 @RestParameter(name = "description", description = "The theme description",
430 isRequired = false, type = Type.TEXT),
431 @RestParameter(name = "bumperActive", description = "Whether the theme bumper is active",
432 isRequired = false, type = Type.BOOLEAN),
433 @RestParameter(name = "trailerActive", description = "Whether the theme trailer is active",
434 isRequired = false, type = Type.BOOLEAN),
435 @RestParameter(name = "titleSlideActive", description = "Whether the theme title slide is active",
436 isRequired = false, type = Type.BOOLEAN),
437 @RestParameter(name = "licenseSlideActive", description = "Whether the theme license slide is active",
438 isRequired = false, type = Type.BOOLEAN),
439 @RestParameter(name = "watermarkActive", description = "Whether the theme watermark is active",
440 isRequired = false, type = Type.BOOLEAN),
441 @RestParameter(name = "bumperFile", description = "The theme bumper file",
442 isRequired = false, type = Type.STRING),
443 @RestParameter(name = "trailerFile", description = "The theme trailer file",
444 isRequired = false, type = Type.STRING),
445 @RestParameter(name = "watermarkFile", description = "The theme watermark file",
446 isRequired = false, type = Type.STRING),
447 @RestParameter(name = "titleSlideBackground", description = "The theme title slide background file",
448 isRequired = false, type = Type.STRING),
449 @RestParameter(name = "licenseSlideBackground", description = "The theme license slide background file",
450 isRequired = false, type = Type.STRING),
451 @RestParameter(name = "titleSlideMetadata", description = "The theme title slide metadata",
452 isRequired = false, type = Type.STRING),
453 @RestParameter(name = "licenseSlideDescription", description = "The theme license slide description",
454 isRequired = false, type = Type.STRING),
455 @RestParameter(name = "watermarkPosition", description = "The theme watermark position",
456 isRequired = false, type = Type.STRING),
457 },
458 responses = {
459 @RestResponse(responseCode = SC_OK, description = "Theme updated"),
460 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the theme has not been found."),
461 })
462 public Response updateTheme(@PathParam("themeId") long themeId, @FormParam("default") Boolean isDefault,
463 @FormParam("name") String name, @FormParam("description") String description,
464 @FormParam("bumperActive") Boolean bumperActive, @FormParam("trailerActive") Boolean trailerActive,
465 @FormParam("titleSlideActive") Boolean titleSlideActive,
466 @FormParam("licenseSlideActive") Boolean licenseSlideActive,
467 @FormParam("watermarkActive") Boolean watermarkActive, @FormParam("bumperFile") String bumperFile,
468 @FormParam("trailerFile") String trailerFile, @FormParam("watermarkFile") String watermarkFile,
469 @FormParam("titleSlideBackground") String titleSlideBackground,
470 @FormParam("licenseSlideBackground") String licenseSlideBackground,
471 @FormParam("titleSlideMetadata") String titleSlideMetadata,
472 @FormParam("licenseSlideDescription") String licenseSlideDescription,
473 @FormParam("watermarkPosition") String watermarkPosition) throws NotFoundException {
474 try {
475 Theme origTheme = themesServiceDatabase.getTheme(themeId);
476
477 if (isDefault == null) {
478 isDefault = origTheme.isDefault();
479 }
480 if (StringUtils.isBlank(name)) {
481 name = origTheme.getName();
482 }
483 if (StringUtils.isEmpty(description)) {
484 description = origTheme.getDescription();
485 }
486 if (bumperActive == null) {
487 bumperActive = origTheme.isBumperActive();
488 }
489 if (StringUtils.isEmpty(bumperFile)) {
490 bumperFile = origTheme.getBumperFile();
491 }
492 if (trailerActive == null) {
493 trailerActive = origTheme.isTrailerActive();
494 }
495 if (StringUtils.isEmpty(trailerFile)) {
496 trailerFile = origTheme.getTrailerFile();
497 }
498 if (titleSlideActive == null) {
499 titleSlideActive = origTheme.isTitleSlideActive();
500 }
501 if (StringUtils.isEmpty(titleSlideMetadata)) {
502 titleSlideMetadata = origTheme.getTitleSlideMetadata();
503 }
504 if (StringUtils.isEmpty(titleSlideBackground)) {
505 titleSlideBackground = origTheme.getTitleSlideBackground();
506 }
507 if (licenseSlideActive == null) {
508 licenseSlideActive = origTheme.isLicenseSlideActive();
509 }
510 if (StringUtils.isEmpty(licenseSlideBackground)) {
511 licenseSlideBackground = origTheme.getLicenseSlideBackground();
512 }
513 if (StringUtils.isEmpty(licenseSlideDescription)) {
514 licenseSlideDescription = origTheme.getLicenseSlideDescription();
515 }
516 if (watermarkActive == null) {
517 watermarkActive = origTheme.isWatermarkActive();
518 }
519 if (StringUtils.isEmpty(watermarkFile)) {
520 watermarkFile = origTheme.getWatermarkFile();
521 }
522 if (StringUtils.isEmpty(watermarkPosition)) {
523 watermarkPosition = origTheme.getWatermarkPosition();
524 }
525
526 Theme theme = new Theme(origTheme.getId(), origTheme.getCreationDate(), isDefault, origTheme.getCreator(), name,
527 StringUtils.trimToNull(description), BooleanUtils.toBoolean(bumperActive),
528 StringUtils.trimToNull(bumperFile), BooleanUtils.toBoolean(trailerActive),
529 StringUtils.trimToNull(trailerFile), BooleanUtils.toBoolean(titleSlideActive),
530 StringUtils.trimToNull(titleSlideMetadata), StringUtils.trimToNull(titleSlideBackground),
531 BooleanUtils.toBoolean(licenseSlideActive), StringUtils.trimToNull(licenseSlideBackground),
532 StringUtils.trimToNull(licenseSlideDescription), BooleanUtils.toBoolean(watermarkActive),
533 StringUtils.trimToNull(watermarkFile), StringUtils.trimToNull(watermarkPosition));
534
535 try {
536 updateReferencedFiles(origTheme, theme);
537 } catch (IOException e) {
538 logger.warn("Error while persisting file: {}", e.getMessage());
539 return R.serverError();
540 } catch (NotFoundException e) {
541 logger.warn("A file that is referenced in theme '{}' was not found: {}", theme, e.getMessage());
542 return R.badRequest("Referenced non-existing file");
543 }
544
545 Theme updatedTheme = themesServiceDatabase.updateTheme(theme);
546 return RestUtils.okJson(themeToJSON(updatedTheme, false));
547 } catch (ThemesServiceDatabaseException e) {
548 logger.error("Unable to update theme {}", themeId, e);
549 return RestUtil.R.serverError();
550 }
551 }
552
553 @DELETE
554 @Path("{themeId}")
555 @RestQuery(
556 name = "deleteTheme",
557 description = "Deletes a theme",
558 returnDescription = "The method doesn't return any content",
559 pathParameters = {
560 @RestParameter(name = "themeId", isRequired = true, description = "The theme identifier",
561 type = RestParameter.Type.INTEGER)
562 },
563 responses = {
564 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the theme has not been found."),
565 @RestResponse(responseCode = SC_NO_CONTENT, description = "The method does not return any content"),
566 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "If the current user is not authorized to "
567 + "perform this action")
568 })
569 public Response deleteTheme(@PathParam("themeId") long themeId) throws NotFoundException, UnauthorizedException {
570 try {
571 Theme theme = themesServiceDatabase.getTheme(themeId);
572 try {
573 deleteReferencedFiles(theme);
574 } catch (IOException e) {
575 logger.warn("Error while deleting referenced file: {}", e.getMessage());
576 return R.serverError();
577 }
578
579 themesServiceDatabase.deleteTheme(themeId);
580 deleteThemeOnSeries(themeId);
581
582 return RestUtil.R.noContent();
583 } catch (NotFoundException e) {
584 logger.warn("Unable to find a theme with id " + themeId);
585 throw e;
586 } catch (ThemesServiceDatabaseException e) {
587 logger.error("Error getting theme {} during delete operation because:", themeId,
588 e);
589 return RestUtil.R.serverError();
590 }
591 }
592
593
594
595
596
597
598
599 private void deleteThemeOnSeries(long themeId) throws UnauthorizedException {
600 SeriesSearchQuery query = new SeriesSearchQuery(securityService.getOrganization().getId(),
601 securityService.getUser()).withTheme(themeId);
602 SearchResult<Series> results = null;
603 try {
604 results = searchIndex.getByQuery(query);
605 } catch (SearchIndexException e) {
606 logger.error("The admin UI Search Index was not able to get the series with theme '{}':", themeId, e);
607 throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
608 }
609 for (SearchResultItem<Series> item : results.getItems()) {
610 String seriesId = item.getSource().getIdentifier();
611 try {
612 seriesService.deleteSeriesProperty(seriesId, SeriesEndpoint.THEME_KEY);
613 } catch (NotFoundException e) {
614 logger.warn("Theme {} already deleted on series {}", themeId, seriesId);
615 } catch (SeriesException e) {
616 logger.error("Unable to remove theme from series {}", seriesId, e);
617 throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
618 }
619 }
620 }
621
622 private void extendStaticFileInfo(String fieldName, String staticFileId, JsonObject json) {
623 if (StringUtils.isNotBlank(staticFileId)) {
624 try {
625 String fileName = staticFileService.getFileName(staticFileId);
626 String fileUrl = staticFileRestService.getStaticFileURL(staticFileId).toString();
627
628 json.addProperty(fieldName + "Name", fileName);
629 json.addProperty(fieldName + "Url", safeString(fileUrl));
630 } catch (IllegalStateException | NotFoundException e) {
631 logger.error("Error retrieving static file '{}'", staticFileId, e);
632 }
633 }
634 }
635
636
637
638
639
640
641
642
643
644
645 private JsonObject themeToJSON(Theme theme, boolean editResponse) {
646 String creator = StringUtils.isNotBlank(theme.getCreator().getName())
647 ? theme.getCreator().getName()
648 : theme.getCreator().getUsername();
649
650 JsonObject json = new JsonObject();
651
652 json.addProperty("id", theme.getId().orElse(-1L));
653 json.addProperty("creationDate", DateTimeSupport.toUTC(theme.getCreationDate().getTime()));
654 json.addProperty("default", theme.isDefault());
655 json.addProperty("name", theme.getName());
656 json.addProperty("creator", creator);
657 json.addProperty("description", safeString(theme.getDescription()));
658 json.addProperty("bumperActive", theme.isBumperActive());
659 json.addProperty("bumperFile", safeString(theme.getBumperFile()));
660 json.addProperty("trailerActive", theme.isTrailerActive());
661 json.addProperty("trailerFile", safeString(theme.getTrailerFile()));
662 json.addProperty("titleSlideActive", theme.isTitleSlideActive());
663 json.addProperty("titleSlideMetadata", safeString(theme.getTitleSlideMetadata()));
664 json.addProperty("titleSlideBackground", safeString(theme.getTitleSlideBackground()));
665 json.addProperty("licenseSlideActive", theme.isLicenseSlideActive());
666 json.addProperty("licenseSlideDescription", safeString(theme.getLicenseSlideDescription()));
667 json.addProperty("licenseSlideBackground", safeString(theme.getLicenseSlideBackground()));
668 json.addProperty("watermarkActive", theme.isWatermarkActive());
669 json.addProperty("watermarkFile", theme.getWatermarkFile() != null ? theme.getWatermarkFile() : "");
670 json.addProperty("watermarkPosition", theme.getWatermarkPosition() != null ? theme.getWatermarkPosition() : "");
671
672 if (editResponse) {
673 extendStaticFileInfo("bumperFile", theme.getBumperFile(), json);
674 extendStaticFileInfo("trailerFile", theme.getTrailerFile(), json);
675 extendStaticFileInfo("titleSlideBackground", theme.getTitleSlideBackground(), json);
676 extendStaticFileInfo("licenseSlideBackground", theme.getLicenseSlideBackground(), json);
677 extendStaticFileInfo("watermarkFile", theme.getWatermarkFile(), json);
678 }
679
680 return json;
681 }
682
683
684
685
686
687
688
689
690
691
692
693 private void persistReferencedFiles(Theme theme) throws NotFoundException, IOException {
694 if (isNotBlank(theme.getBumperFile())) {
695 staticFileService.persistFile(theme.getBumperFile());
696 }
697 if (isNotBlank(theme.getLicenseSlideBackground())) {
698 staticFileService.persistFile(theme.getLicenseSlideBackground());
699 }
700 if (isNotBlank(theme.getTitleSlideBackground())) {
701 staticFileService.persistFile(theme.getTitleSlideBackground());
702 }
703 if (isNotBlank(theme.getTrailerFile())) {
704 staticFileService.persistFile(theme.getTrailerFile());
705 }
706 if (isNotBlank(theme.getWatermarkFile())) {
707 staticFileService.persistFile(theme.getWatermarkFile());
708 }
709 }
710
711
712
713
714
715
716
717
718
719
720
721 private void deleteReferencedFiles(Theme theme) throws NotFoundException, IOException {
722 if (isNotBlank(theme.getBumperFile())) {
723 staticFileService.deleteFile(theme.getBumperFile());
724 }
725 if (isNotBlank(theme.getLicenseSlideBackground())) {
726 staticFileService.deleteFile(theme.getLicenseSlideBackground());
727 }
728 if (isNotBlank(theme.getTitleSlideBackground())) {
729 staticFileService.deleteFile(theme.getTitleSlideBackground());
730 }
731 if (isNotBlank(theme.getTrailerFile())) {
732 staticFileService.deleteFile(theme.getTrailerFile());
733 }
734 if (isNotBlank(theme.getWatermarkFile())) {
735 staticFileService.deleteFile(theme.getWatermarkFile());
736 }
737 }
738
739
740
741
742
743
744
745
746
747
748
749
750
751 private void updateReferencedFiles(Theme original, Theme updated) throws NotFoundException, IOException {
752 updateReferencedFile(original.getBumperFile(), updated.getBumperFile());
753 updateReferencedFile(original.getLicenseSlideBackground(), updated.getLicenseSlideBackground());
754 updateReferencedFile(original.getTitleSlideBackground(), updated.getTitleSlideBackground());
755 updateReferencedFile(original.getTrailerFile(), updated.getTrailerFile());
756 updateReferencedFile(original.getWatermarkFile(), updated.getWatermarkFile());
757 }
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772 private void updateReferencedFile(String original, String updated) throws NotFoundException, IOException {
773 if (EqualsUtil.ne(original, updated)) {
774 if (isNotBlank(original)) {
775 staticFileService.deleteFile(original);
776 }
777 if (isNotBlank(updated)) {
778 staticFileService.persistFile(updated);
779 }
780 }
781 }
782
783 }