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