/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.adminui.endpoint; import static com.entwinemedia.fn.data.Opt.nul; import static com.entwinemedia.fn.data.json.Jsons.arr; import static com.entwinemedia.fn.data.json.Jsons.f; import static com.entwinemedia.fn.data.json.Jsons.obj; import static com.entwinemedia.fn.data.json.Jsons.v; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.trimToNull; import static org.opencastproject.index.service.util.RestUtils.notFound; import static org.opencastproject.index.service.util.RestUtils.okJson; import static org.opencastproject.index.service.util.RestUtils.okJsonList; import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING; import org.opencastproject.adminui.impl.index.AdminUISearchIndex; import org.opencastproject.adminui.util.QueryPreprocessor; import org.opencastproject.index.service.impl.index.series.Series; import org.opencastproject.index.service.impl.index.series.SeriesSearchQuery; import org.opencastproject.index.service.impl.index.theme.ThemeIndexSchema; import org.opencastproject.index.service.impl.index.theme.ThemeSearchQuery; import org.opencastproject.index.service.resources.list.query.ThemesListQuery; import org.opencastproject.index.service.util.RestUtils; import org.opencastproject.matterhorn.search.SearchIndexException; import org.opencastproject.matterhorn.search.SearchResult; import org.opencastproject.matterhorn.search.SearchResultItem; import org.opencastproject.matterhorn.search.SortCriterion; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.UnauthorizedException; import org.opencastproject.security.api.User; import org.opencastproject.series.api.SeriesException; import org.opencastproject.series.api.SeriesService; import org.opencastproject.staticfiles.api.StaticFileService; import org.opencastproject.staticfiles.endpoint.StaticFileRestService; import org.opencastproject.themes.Theme; import org.opencastproject.themes.ThemesServiceDatabase; import org.opencastproject.themes.persistence.ThemesServiceDatabaseException; import org.opencastproject.util.DateTimeSupport; import org.opencastproject.util.EqualsUtil; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.RestUtil; import org.opencastproject.util.RestUtil.R; import org.opencastproject.util.data.Option; import org.opencastproject.util.doc.rest.RestParameter; import org.opencastproject.util.doc.rest.RestParameter.Type; import org.opencastproject.util.doc.rest.RestQuery; import org.opencastproject.util.doc.rest.RestResponse; import org.opencastproject.util.doc.rest.RestService; import com.entwinemedia.fn.data.Opt; import com.entwinemedia.fn.data.json.Field; import com.entwinemedia.fn.data.json.JValue; import com.entwinemedia.fn.data.json.Jsons; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @Path("/") @RestService(name = "themes", title = "Themes facade service", abstractText = "Provides operations for the themes", notes = { "This service offers the default themes CRUD Operations for the admin UI.", "<strong>Important:</strong> " + "<em>This service is for exclusive use by the module matterhorn-admin-ui-ng. Its API might change " + "anytime without prior notice. Any dependencies other than the admin UI will be strictly ignored. " + "DO NOT use this for integration of third-party applications.<em>"}) public class ThemesEndpoint { /** The logging facility */ private static final Logger logger = LoggerFactory.getLogger(ThemesEndpoint.class); /** The themes service database */ private ThemesServiceDatabase themesServiceDatabase; /** The security service */ private SecurityService securityService; /** The admin UI search index */ private AdminUISearchIndex searchIndex; /** The series service */ private SeriesService seriesService; /** The static file service */ private StaticFileService staticFileService; /** The static file REST service */ private StaticFileRestService staticFileRestService; /** OSGi callback for the themes service database. */ public void setThemesServiceDatabase(ThemesServiceDatabase themesServiceDatabase) { this.themesServiceDatabase = themesServiceDatabase; } /** OSGi callback for the security service. */ public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** OSGi DI. */ public void setIndex(AdminUISearchIndex index) { this.searchIndex = index; } /** OSGi DI. */ public void setSeriesService(SeriesService seriesService) { this.seriesService = seriesService; } /** OSGi DI. */ public void setStaticFileService(StaticFileService staticFileService) { this.staticFileService = staticFileService; } /** OSGi DI. */ public void setStaticFileRestService(StaticFileRestService staticFileRestService) { this.staticFileRestService = staticFileRestService; } public void activate(BundleContext bundleContext) { logger.info("Activate themes endpoint"); } @GET @Produces({ MediaType.APPLICATION_JSON }) @Path("themes.json") @RestQuery(name = "getThemes", description = "Return all of the known themes on the system", restParameters = { @RestParameter(name = "filter", isRequired = false, description = "The filter used for the query. They should be formated like that: 'filter1:value1,filter2:value2'", type = STRING), @RestParameter(defaultValue = "0", description = "The maximum number of items to return per page.", isRequired = false, name = "limit", type = RestParameter.Type.INTEGER), @RestParameter(defaultValue = "0", description = "The page number.", isRequired = false, name = "offset", type = RestParameter.Type.INTEGER), @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) }, reponses = { @RestResponse(description = "A JSON representation of the themes", responseCode = HttpServletResponse.SC_OK) }, returnDescription = "") public Response getThemes(@QueryParam("filter") String filter, @QueryParam("limit") int limit, @QueryParam("offset") int offset, @QueryParam("sort") String sort) { Option<Integer> optLimit = Option.option(limit); Option<Integer> optOffset = Option.option(offset); Option<String> optSort = Option.option(trimToNull(sort)); ThemeSearchQuery query = new ThemeSearchQuery(securityService.getOrganization().getId(), securityService.getUser()); // If the limit is set to 0, this is not taken into account if (optLimit.isSome() && limit == 0) { optLimit = Option.none(); } if (optLimit.isSome()) query.withLimit(optLimit.get()); if (optOffset.isSome()) query.withOffset(offset); Map<String, String> filters = RestUtils.parseFilter(filter); for (String name : filters.keySet()) { if (ThemesListQuery.FILTER_CREATOR_NAME.equals(name)) query.withCreator(filters.get(name)); if (ThemesListQuery.FILTER_TEXT_NAME.equals(name)) query.withText(QueryPreprocessor.sanitize(filters.get(name))); } if (optSort.isSome()) { Set<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(optSort.get()); for (SortCriterion criterion : sortCriteria) { switch (criterion.getFieldName()) { case ThemeIndexSchema.NAME: query.sortByName(criterion.getOrder()); break; case ThemeIndexSchema.DESCRIPTION: query.sortByDescription(criterion.getOrder()); break; case ThemeIndexSchema.CREATOR: query.sortByCreator(criterion.getOrder()); break; case ThemeIndexSchema.DEFAULT: query.sortByDefault(criterion.getOrder()); break; case ThemeIndexSchema.CREATION_DATE: query.sortByCreatedDateTime(criterion.getOrder()); break; default: logger.info("Unknown sort criteria {}", criterion.getFieldName()); return Response.status(SC_BAD_REQUEST).build(); } } } logger.trace("Using Query: " + query.toString()); SearchResult<org.opencastproject.index.service.impl.index.theme.Theme> results = null; try { results = searchIndex.getByQuery(query); } catch (SearchIndexException e) { logger.error("The admin UI Search Index was not able to get the themes list: {}", e); return RestUtil.R.serverError(); } List<JValue> themesJSON = new ArrayList<JValue>(); // If the results list if empty, we return already a response. if (results.getPageSize() == 0) { logger.debug("No themes match the given filters."); return okJsonList(themesJSON, nul(offset).getOr(0), nul(limit).getOr(0), 0); } for (SearchResultItem<org.opencastproject.index.service.impl.index.theme.Theme> item : results.getItems()) { org.opencastproject.index.service.impl.index.theme.Theme theme = item.getSource(); themesJSON.add(themeToJSON(theme, false)); } return okJsonList(themesJSON, nul(offset).getOr(0), nul(limit).getOr(0), results.getHitCount()); } @GET @Path("{themeId}.json") @Produces(MediaType.APPLICATION_JSON) @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) }, reponses = { @RestResponse(description = "Returns the theme as JSON", responseCode = HttpServletResponse.SC_OK), @RestResponse(description = "No theme with this identifier was found.", responseCode = HttpServletResponse.SC_NOT_FOUND) }) public Response getThemeResponse(@PathParam("themeId") long id) throws Exception { Opt<org.opencastproject.index.service.impl.index.theme.Theme> theme = getTheme(id); if (theme.isNone()) return notFound("Cannot find a theme with id '%s'", id); return okJson(themeToJSON(theme.get(), true)); } @GET @Path("{themeId}/usage.json") @Produces(MediaType.APPLICATION_JSON) @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) }, reponses = { @RestResponse(description = "Returns the theme usage as JSON", responseCode = HttpServletResponse.SC_OK), @RestResponse(description = "Theme with the given id does not exist", responseCode = HttpServletResponse.SC_NOT_FOUND) }) public Response getThemeUsage(@PathParam("themeId") long themeId) throws Exception { Opt<org.opencastproject.index.service.impl.index.theme.Theme> theme = getTheme(themeId); if (theme.isNone()) return notFound("Cannot find a theme with id {}", themeId); SeriesSearchQuery query = new SeriesSearchQuery(securityService.getOrganization().getId(), securityService.getUser()).withTheme(themeId); SearchResult<Series> results = null; try { results = searchIndex.getByQuery(query); } catch (SearchIndexException e) { logger.error("The admin UI Search Index was not able to get the series with theme '{}': {}", themeId, ExceptionUtils.getStackTrace(e)); return RestUtil.R.serverError(); } List<JValue> seriesValues = new ArrayList<JValue>(); for (SearchResultItem<Series> item : results.getItems()) { Series series = item.getSource(); seriesValues.add(obj(f("id", v(series.getIdentifier())), f("title", v(series.getTitle())))); } return okJson(obj(f("series", arr(seriesValues)))); } @POST @Path("") @RestQuery(name = "createTheme", description = "Add a theme", returnDescription = "Return the created theme", restParameters = { @RestParameter(name = "default", description = "Whether the theme is default", isRequired = true, type = Type.BOOLEAN), @RestParameter(name = "name", description = "The theme name", isRequired = true, type = Type.STRING), @RestParameter(name = "description", description = "The theme description", isRequired = false, type = Type.TEXT), @RestParameter(name = "bumperActive", description = "Whether the theme bumper is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "trailerActive", description = "Whether the theme trailer is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "titleSlideActive", description = "Whether the theme title slide is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "licenseSlideActive", description = "Whether the theme license slide is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "watermarkActive", description = "Whether the theme watermark is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "bumperFile", description = "The theme bumper file", isRequired = false, type = Type.STRING), @RestParameter(name = "trailerFile", description = "The theme trailer file", isRequired = false, type = Type.STRING), @RestParameter(name = "watermarkFile", description = "The theme watermark file", isRequired = false, type = Type.STRING), @RestParameter(name = "titleSlideBackground", description = "The theme title slide background file", isRequired = false, type = Type.STRING), @RestParameter(name = "licenseSlideBackground", description = "The theme license slide background file", isRequired = false, type = Type.STRING), @RestParameter(name = "titleSlideMetadata", description = "The theme title slide metadata", isRequired = false, type = Type.STRING), @RestParameter(name = "licenseSlideDescription", description = "The theme license slide description", isRequired = false, type = Type.STRING), @RestParameter(name = "watermarkPosition", description = "The theme watermark position", isRequired = false, type = Type.STRING), }, reponses = { @RestResponse(responseCode = SC_OK, description = "Theme created"), @RestResponse(responseCode = SC_BAD_REQUEST, description = "The theme references a non-existing file") }) public Response createTheme(@FormParam("default") boolean isDefault, @FormParam("name") String name, @FormParam("description") String description, @FormParam("bumperActive") Boolean bumperActive, @FormParam("trailerActive") Boolean trailerActive, @FormParam("titleSlideActive") Boolean titleSlideActive, @FormParam("licenseSlideActive") Boolean licenseSlideActive, @FormParam("watermarkActive") Boolean watermarkActive, @FormParam("bumperFile") String bumperFile, @FormParam("trailerFile") String trailerFile, @FormParam("watermarkFile") String watermarkFile, @FormParam("titleSlideBackground") String titleSlideBackground, @FormParam("licenseSlideBackground") String licenseSlideBackground, @FormParam("titleSlideMetadata") String titleSlideMetadata, @FormParam("licenseSlideDescription") String licenseSlideDescription, @FormParam("watermarkPosition") String watermarkPosition) { User creator = securityService.getUser(); Theme theme = new Theme(Option.<Long> none(), new Date(), isDefault, creator, name, StringUtils.trimToNull(description), BooleanUtils.toBoolean(bumperActive), StringUtils.trimToNull(bumperFile), BooleanUtils.toBoolean(trailerActive), StringUtils.trimToNull(trailerFile), BooleanUtils.toBoolean(titleSlideActive), StringUtils.trimToNull(titleSlideMetadata), StringUtils.trimToNull(titleSlideBackground), BooleanUtils.toBoolean(licenseSlideActive), StringUtils.trimToNull(licenseSlideBackground), StringUtils.trimToNull(licenseSlideDescription), BooleanUtils.toBoolean(watermarkActive), StringUtils.trimToNull(watermarkFile), StringUtils.trimToNull(watermarkPosition)); try { persistReferencedFiles(theme); } catch (NotFoundException e) { logger.warn("A file that is referenced in theme '{}' was not found: {}", theme, e.getMessage()); return R.badRequest("Referenced non-existing file"); } catch (IOException e) { logger.warn("Error while persisting file: {}", e.getMessage()); return R.serverError(); } try { Theme createdTheme = themesServiceDatabase.updateTheme(theme); return RestUtils.okJson(themeToJSON(createdTheme)); } catch (ThemesServiceDatabaseException e) { logger.error("Unable to create a theme"); return RestUtil.R.serverError(); } } @PUT @Path("{themeId}") @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 = { @RestParameter(name = "default", description = "Whether the theme is default", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "name", description = "The theme name", isRequired = false, type = Type.STRING), @RestParameter(name = "description", description = "The theme description", isRequired = false, type = Type.TEXT), @RestParameter(name = "bumperActive", description = "Whether the theme bumper is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "trailerActive", description = "Whether the theme trailer is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "titleSlideActive", description = "Whether the theme title slide is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "licenseSlideActive", description = "Whether the theme license slide is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "watermarkActive", description = "Whether the theme watermark is active", isRequired = false, type = Type.BOOLEAN), @RestParameter(name = "bumperFile", description = "The theme bumper file", isRequired = false, type = Type.STRING), @RestParameter(name = "trailerFile", description = "The theme trailer file", isRequired = false, type = Type.STRING), @RestParameter(name = "watermarkFile", description = "The theme watermark file", isRequired = false, type = Type.STRING), @RestParameter(name = "titleSlideBackground", description = "The theme title slide background file", isRequired = false, type = Type.STRING), @RestParameter(name = "licenseSlideBackground", description = "The theme license slide background file", isRequired = false, type = Type.STRING), @RestParameter(name = "titleSlideMetadata", description = "The theme title slide metadata", isRequired = false, type = Type.STRING), @RestParameter(name = "licenseSlideDescription", description = "The theme license slide description", isRequired = false, type = Type.STRING), @RestParameter(name = "watermarkPosition", description = "The theme watermark position", isRequired = false, type = Type.STRING), }, reponses = { @RestResponse(responseCode = SC_OK, description = "Theme updated"), @RestResponse(responseCode = SC_NOT_FOUND, description = "If the theme has not been found."), }) public Response updateTheme(@PathParam("themeId") long themeId, @FormParam("default") Boolean isDefault, @FormParam("name") String name, @FormParam("description") String description, @FormParam("bumperActive") Boolean bumperActive, @FormParam("trailerActive") Boolean trailerActive, @FormParam("titleSlideActive") Boolean titleSlideActive, @FormParam("licenseSlideActive") Boolean licenseSlideActive, @FormParam("watermarkActive") Boolean watermarkActive, @FormParam("bumperFile") String bumperFile, @FormParam("trailerFile") String trailerFile, @FormParam("watermarkFile") String watermarkFile, @FormParam("titleSlideBackground") String titleSlideBackground, @FormParam("licenseSlideBackground") String licenseSlideBackground, @FormParam("titleSlideMetadata") String titleSlideMetadata, @FormParam("licenseSlideDescription") String licenseSlideDescription, @FormParam("watermarkPosition") String watermarkPosition) throws NotFoundException { try { Theme origTheme = themesServiceDatabase.getTheme(themeId); if (isDefault == null) isDefault = origTheme.isDefault(); if (StringUtils.isBlank(name)) name = origTheme.getName(); if (StringUtils.isEmpty(description)) description = origTheme.getDescription(); if (bumperActive == null) bumperActive = origTheme.isBumperActive(); if (StringUtils.isEmpty(bumperFile)) bumperFile = origTheme.getBumperFile(); if (trailerActive == null) trailerActive = origTheme.isTrailerActive(); if (StringUtils.isEmpty(trailerFile)) trailerFile = origTheme.getTrailerFile(); if (titleSlideActive == null) titleSlideActive = origTheme.isTitleSlideActive(); if (StringUtils.isEmpty(titleSlideMetadata)) titleSlideMetadata = origTheme.getTitleSlideMetadata(); if (StringUtils.isEmpty(titleSlideBackground)) titleSlideBackground = origTheme.getTitleSlideBackground(); if (licenseSlideActive == null) licenseSlideActive = origTheme.isLicenseSlideActive(); if (StringUtils.isEmpty(licenseSlideBackground)) licenseSlideBackground = origTheme.getLicenseSlideBackground(); if (StringUtils.isEmpty(licenseSlideDescription)) licenseSlideDescription = origTheme.getLicenseSlideDescription(); if (watermarkActive == null) watermarkActive = origTheme.isWatermarkActive(); if (StringUtils.isEmpty(watermarkFile)) watermarkFile = origTheme.getWatermarkFile(); if (StringUtils.isEmpty(watermarkPosition)) watermarkPosition = origTheme.getWatermarkPosition(); Theme theme = new Theme(origTheme.getId(), origTheme.getCreationDate(), isDefault, origTheme.getCreator(), name, StringUtils.trimToNull(description), BooleanUtils.toBoolean(bumperActive), StringUtils.trimToNull(bumperFile), BooleanUtils.toBoolean(trailerActive), StringUtils.trimToNull(trailerFile), BooleanUtils.toBoolean(titleSlideActive), StringUtils.trimToNull(titleSlideMetadata), StringUtils.trimToNull(titleSlideBackground), BooleanUtils.toBoolean(licenseSlideActive), StringUtils.trimToNull(licenseSlideBackground), StringUtils.trimToNull(licenseSlideDescription), BooleanUtils.toBoolean(watermarkActive), StringUtils.trimToNull(watermarkFile), StringUtils.trimToNull(watermarkPosition)); try { updateReferencedFiles(origTheme, theme); } catch (IOException e) { logger.warn("Error while persisting file: {}", e.getMessage()); return R.serverError(); } catch (NotFoundException e) { logger.warn("A file that is referenced in theme '{}' was not found: {}", theme, e.getMessage()); return R.badRequest("Referenced non-existing file"); } Theme updatedTheme = themesServiceDatabase.updateTheme(theme); return RestUtils.okJson(themeToJSON(updatedTheme)); } catch (ThemesServiceDatabaseException e) { logger.error("Unable to update theme {}: {}", themeId, ExceptionUtils.getStackTrace(e)); return RestUtil.R.serverError(); } } @DELETE @Path("{themeId}") @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) }, reponses = { @RestResponse(responseCode = SC_NOT_FOUND, description = "If the theme has not been found."), @RestResponse(responseCode = SC_NO_CONTENT, description = "The method does not return any content"), @RestResponse(responseCode = SC_UNAUTHORIZED, description = "If the current user is not authorized to perform this action") }) public Response deleteTheme(@PathParam("themeId") long themeId) throws NotFoundException, UnauthorizedException { try { Theme theme = themesServiceDatabase.getTheme(themeId); try { deleteReferencedFiles(theme); } catch (IOException e) { logger.warn("Error while deleting referenced file: {}", e.getMessage()); return R.serverError(); } themesServiceDatabase.deleteTheme(themeId); deleteThemeOnSeries(themeId); return RestUtil.R.noContent(); } catch (NotFoundException e) { logger.warn("Unable to find a theme with id " + themeId); throw e; } catch (ThemesServiceDatabaseException e) { logger.error("Error getting theme {} during delete operation because: {}", themeId, ExceptionUtils.getStackTrace(e)); return RestUtil.R.serverError(); } } /** * Deletes all related series theme entries * * @param themeId * the theme id */ private void deleteThemeOnSeries(long themeId) throws UnauthorizedException { SeriesSearchQuery query = new SeriesSearchQuery(securityService.getOrganization().getId(), securityService.getUser()).withTheme(themeId); SearchResult<Series> results = null; try { results = searchIndex.getByQuery(query); } catch (SearchIndexException e) { logger.error("The admin UI Search Index was not able to get the series with theme '{}': {}", themeId, ExceptionUtils.getStackTrace(e)); throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR); } for (SearchResultItem<Series> item : results.getItems()) { String seriesId = item.getSource().getIdentifier(); try { seriesService.deleteSeriesProperty(seriesId, SeriesEndpoint.THEME_KEY); } catch (NotFoundException e) { logger.warn("Theme {} already deleted on series {}", themeId, seriesId); } catch (SeriesException e) { logger.error("Unable to remove theme from series {}: {}", seriesId, ExceptionUtils.getStackTrace(e)); throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR); } } } /** * Get a single theme * * @param id * the theme id * @return a theme or none if not found, wrapped in an option * @throws SearchIndexException */ private Opt<org.opencastproject.index.service.impl.index.theme.Theme> getTheme(long id) throws SearchIndexException { SearchResult<org.opencastproject.index.service.impl.index.theme.Theme> result = searchIndex .getByQuery(new ThemeSearchQuery(securityService.getOrganization().getId(), securityService.getUser()) .withIdentifier(id)); if (result.getPageSize() == 0) { logger.debug("Didn't find theme with id {}", id); return Opt.<org.opencastproject.index.service.impl.index.theme.Theme> none(); } return Opt.some(result.getItems()[0].getSource()); } /** * Returns the JSON representation of this theme. * * @param theme * the theme * @param editResponse * whether the returning representation should contain edit information * @return the JSON representation of this theme. */ private JValue themeToJSON(org.opencastproject.index.service.impl.index.theme.Theme theme, boolean editResponse) { List<Field> fields = new ArrayList<Field>(); fields.add(f("id", v(theme.getIdentifier()))); fields.add(f("creationDate", v(DateTimeSupport.toUTC(theme.getCreationDate().getTime())))); fields.add(f("default", v(theme.isDefault()))); fields.add(f("name", v(theme.getName()))); fields.add(f("creator", v(theme.getCreator()))); fields.add(f("description", v(theme.getDescription(), Jsons.BLANK))); fields.add(f("bumperActive", v(theme.isBumperActive()))); fields.add(f("bumperFile", v(theme.getBumperFile(), Jsons.BLANK))); fields.add(f("trailerActive", v(theme.isTrailerActive()))); fields.add(f("trailerFile", v(theme.getTrailerFile(), Jsons.BLANK))); fields.add(f("titleSlideActive", v(theme.isTitleSlideActive()))); fields.add(f("titleSlideMetadata", v(theme.getTitleSlideMetadata(), Jsons.BLANK))); fields.add(f("titleSlideBackground", v(theme.getTitleSlideBackground(), Jsons.BLANK))); fields.add(f("licenseSlideActive", v(theme.isLicenseSlideActive()))); fields.add(f("licenseSlideDescription", v(theme.getLicenseSlideDescription(), Jsons.BLANK))); fields.add(f("licenseSlideBackground", v(theme.getLicenseSlideBackground(), Jsons.BLANK))); fields.add(f("watermarkActive", v(theme.isWatermarkActive()))); fields.add(f("watermarkFile", v(theme.getWatermarkFile(), Jsons.BLANK))); fields.add(f("watermarkPosition", v(theme.getWatermarkPosition(), Jsons.BLANK))); if (editResponse) { extendStaticFileInfo("bumperFile", theme.getBumperFile(), fields); extendStaticFileInfo("trailerFile", theme.getTrailerFile(), fields); extendStaticFileInfo("titleSlideBackground", theme.getTitleSlideBackground(), fields); extendStaticFileInfo("licenseSlideBackground", theme.getLicenseSlideBackground(), fields); extendStaticFileInfo("watermarkFile", theme.getWatermarkFile(), fields); } return obj(fields); } private void extendStaticFileInfo(String fieldName, String staticFileId, List<Field> fields) { if (StringUtils.isNotBlank(staticFileId)) { try { fields.add(f(fieldName.concat("Name"), v(staticFileService.getFileName(staticFileId)))); fields.add(f(fieldName.concat("Url"), v(staticFileRestService.getStaticFileURL(staticFileId).toString(), Jsons.BLANK))); } catch (IllegalStateException | NotFoundException e) { logger.error("Error retreiving static file '{}' : {}", staticFileId, ExceptionUtils.getStackTrace(e)); } } } /** * @return The JSON representation of this theme. */ private JValue themeToJSON(Theme theme) { String creator = StringUtils.isNotBlank(theme.getCreator().getName()) ? theme.getCreator().getName() : theme .getCreator().getUsername(); List<Field> fields = new ArrayList<Field>(); fields.add(f("id", v(theme.getId().getOrElse(-1L)))); fields.add(f("creationDate", v(DateTimeSupport.toUTC(theme.getCreationDate().getTime())))); fields.add(f("default", v(theme.isDefault()))); fields.add(f("name", v(theme.getName()))); fields.add(f("creator", v(creator))); fields.add(f("description", v(theme.getDescription(), Jsons.BLANK))); fields.add(f("bumperActive", v(theme.isBumperActive()))); fields.add(f("bumperFile", v(theme.getBumperFile(), Jsons.BLANK))); fields.add(f("trailerActive", v(theme.isTrailerActive()))); fields.add(f("trailerFile", v(theme.getTrailerFile(), Jsons.BLANK))); fields.add(f("titleSlideActive", v(theme.isTitleSlideActive()))); fields.add(f("titleSlideMetadata", v(theme.getTitleSlideMetadata(), Jsons.BLANK))); fields.add(f("titleSlideBackground", v(theme.getTitleSlideBackground(), Jsons.BLANK))); fields.add(f("licenseSlideActive", v(theme.isLicenseSlideActive()))); fields.add(f("licenseSlideDescription", v(theme.getLicenseSlideDescription(), Jsons.BLANK))); fields.add(f("licenseSlideBackground", v(theme.getLicenseSlideBackground(), Jsons.BLANK))); fields.add(f("watermarkActive", v(theme.isWatermarkActive()))); fields.add(f("watermarkFile", v(theme.getWatermarkFile(), Jsons.BLANK))); fields.add(f("watermarkPosition", v(theme.getWatermarkPosition(), Jsons.BLANK))); return obj(fields); } /** * Persist all files that are referenced in the theme. * * @param theme * The theme * @throws NotFoundException * If a referenced file is not found. * @throws IOException * If there was an error while persisting the file. */ private void persistReferencedFiles(Theme theme) throws NotFoundException, IOException { if (isNotBlank(theme.getBumperFile())) staticFileService.persistFile(theme.getBumperFile()); if (isNotBlank(theme.getLicenseSlideBackground())) staticFileService.persistFile(theme.getLicenseSlideBackground()); if (isNotBlank(theme.getTitleSlideBackground())) staticFileService.persistFile(theme.getTitleSlideBackground()); if (isNotBlank(theme.getTrailerFile())) staticFileService.persistFile(theme.getTrailerFile()); if (isNotBlank(theme.getWatermarkFile())) staticFileService.persistFile(theme.getWatermarkFile()); } /** * Delete all files that are referenced in the theme. * * @param theme * The theme * @throws NotFoundException * If a referenced file is not found. * @throws IOException * If there was an error while persisting the file. */ private void deleteReferencedFiles(Theme theme) throws NotFoundException, IOException { if (isNotBlank(theme.getBumperFile())) staticFileService.deleteFile(theme.getBumperFile()); if (isNotBlank(theme.getLicenseSlideBackground())) staticFileService.deleteFile(theme.getLicenseSlideBackground()); if (isNotBlank(theme.getTitleSlideBackground())) staticFileService.deleteFile(theme.getTitleSlideBackground()); if (isNotBlank(theme.getTrailerFile())) staticFileService.deleteFile(theme.getTrailerFile()); if (isNotBlank(theme.getWatermarkFile())) staticFileService.deleteFile(theme.getWatermarkFile()); } /** * Update all files that have changed between {@code original} and {@code updated}. * * @param original * The original theme * @param updated * The updated theme * @throws NotFoundException * If one of the referenced files could not be found. * @throws IOException * If there was an error while updating the referenced files. */ private void updateReferencedFiles(Theme original, Theme updated) throws NotFoundException, IOException { updateReferencedFile(original.getBumperFile(), updated.getBumperFile()); updateReferencedFile(original.getLicenseSlideBackground(), updated.getLicenseSlideBackground()); updateReferencedFile(original.getTitleSlideBackground(), updated.getTitleSlideBackground()); updateReferencedFile(original.getTrailerFile(), updated.getTrailerFile()); updateReferencedFile(original.getWatermarkFile(), updated.getWatermarkFile()); } /** * If the file resource has changed between {@code original} and {@code updated}, the original file is deleted and the * updated one persisted. * * @param original * The UUID of the original file * @param updated * The UUID of the updated file * @throws NotFoundException * If the file could not be found * @throws IOException * If there was an error while persisting or deleting one of the files. */ private void updateReferencedFile(String original, String updated) throws NotFoundException, IOException { if (EqualsUtil.ne(original, updated)) { if (isNotBlank(original)) staticFileService.deleteFile(original); if (isNotBlank(updated)) staticFileService.persistFile(updated); } } }