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