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