ThemesServiceDatabaseImpl.java
/*
* 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.themes.persistence;
import static org.opencastproject.db.Queries.namedQuery;
import org.opencastproject.db.DBSession;
import org.opencastproject.db.DBSessionFactory;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UserDirectoryService;
import org.opencastproject.themes.Theme;
import org.opencastproject.themes.ThemesServiceDatabase;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.requests.SortCriterion;
import org.apache.commons.lang3.tuple.Pair;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
/**
* Implements {@link ThemesServiceDatabase}. Defines permanent storage for themes.
*/
@Component(
immediate = true,
service = { ThemesServiceDatabase.class },
property = {
"service.description=Themes Database Service"
}
)
public class ThemesServiceDatabaseImpl implements ThemesServiceDatabase {
public static final String PERSISTENCE_UNIT = "org.opencastproject.themes";
/** Logging utilities */
private static final Logger logger = LoggerFactory.getLogger(ThemesServiceDatabaseImpl.class);
/** Factory used to create {@link EntityManager}s for transactions */
protected EntityManagerFactory emf;
protected DBSessionFactory dbSessionFactory;
protected DBSession db;
/** The security service */
protected SecurityService securityService;
/** The user directory service */
protected UserDirectoryService userDirectoryService;
/** The component context for this themes service database */
private ComponentContext cc;
/**
* Creates {@link EntityManagerFactory} using persistence provider and properties passed via OSGi.
*
* @param cc
*/
@Activate
public void activate(ComponentContext cc) {
logger.info("Activating persistence manager for themes");
this.cc = cc;
db = dbSessionFactory.createSession(emf);
}
/** OSGi DI */
@Reference(target = "(osgi.unit.name=org.opencastproject.themes)")
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
@Reference
public void setDBSessionFactory(DBSessionFactory dbSessionFactory) {
this.dbSessionFactory = dbSessionFactory;
}
/**
* OSGi callback to set the security service.
*
* @param securityService
* the security service
*/
@Reference
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* OSGi callback to set the user directory service
*
* @param userDirectoryService
* the user directory service
*/
@Reference
public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
this.userDirectoryService = userDirectoryService;
}
@Override
public Theme getTheme(long id) throws ThemesServiceDatabaseException, NotFoundException {
try {
return db.exec(getThemeDtoQuery(id))
.map(t -> t.toTheme(userDirectoryService))
.orElseThrow(() -> new NotFoundException("No theme with id=" + id + " exists"));
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
logger.error("Could not get theme", e);
throw new ThemesServiceDatabaseException(e);
}
}
private List<Theme> getThemes() throws ThemesServiceDatabaseException {
try {
String orgId = securityService.getOrganization().getId();
return db.exec(namedQuery.findAll(
"Themes.findByOrg",
ThemeDto.class,
Pair.of("org", orgId)
)).stream()
.map(t -> t.toTheme(userDirectoryService))
.collect(Collectors.toList());
} catch (Exception e) {
logger.error("Could not get themes", e);
throw new ThemesServiceDatabaseException(e);
}
}
public List<Theme> findThemes(
Optional<Integer> limit,
Optional<Integer> offset,
ArrayList<SortCriterion> sortCriteria,
Optional<String> creatorFilter,
Optional<String> textFilter
) {
String orgId = securityService.getOrganization().getId();
return db.execTxChecked(em -> {
CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<ThemeDto> query = cb.createQuery(ThemeDto.class);
Root<ThemeDto> theme = query.from(ThemeDto.class);
query.select(theme);
query.distinct(true);
// filter
List<Predicate> conditions = new ArrayList<>();
conditions.add(cb.equal(theme.get("organization"), orgId));
// exact match, case sensitive
if (creatorFilter.isPresent()) {
conditions.add(cb.equal(theme.get("username"), creatorFilter.get()));
}
// not exact match, case-insensitive, each token needs to match at least one field
if (textFilter.isPresent()) {
List<Predicate> fulltextConditions = new ArrayList<>();
String[] tokens = textFilter.get().split("\\s+");
for (String token: tokens) {
List<Predicate> fieldConditions = new ArrayList<>();
Expression<String> literal = cb.literal("%" + token + "%");
fieldConditions.add(cb.like(cb.lower(theme.get("username")), cb.lower(literal)));
fieldConditions.add(cb.like(cb.lower(theme.get("name")), cb.lower(literal)));
fieldConditions.add(cb.like(cb.lower(theme.get("description")), cb.lower(literal)));
// token needs to match at least one field
fulltextConditions.add(cb.or(fieldConditions.toArray(new Predicate[0])));
}
// all token have to match something
// (different to fulltext search for Elasticsearch, where only one token has to match!)
conditions.add(cb.and(fulltextConditions.toArray(new Predicate[0])));
}
query.where(cb.and(conditions.toArray(new Predicate[0])));
// sort
List<Order> orders = new ArrayList<>();
for (SortCriterion criterion : sortCriteria) {
String fieldName = criterion.getFieldName();
switch(fieldName) {
case "name":
break;
case "description":
break;
case "creator":
fieldName = "username";
break;
case "default":
fieldName = "isDefault";
break;
case "creation_date":
fieldName = "creationDate";
break;
default:
throw new IllegalArgumentException("Sorting criterion " + criterion.getFieldName() + " is not supported "
+ "for themes.");
}
Expression expression = theme.get(fieldName);
if (criterion.getOrder() == SortCriterion.Order.Ascending) {
orders.add(cb.asc(expression));
} else if (criterion.getOrder() == SortCriterion.Order.Descending) {
orders.add(cb.desc(expression));
}
}
query.orderBy(orders);
// other
TypedQuery<ThemeDto> typedQuery = em.createQuery(query);
if (limit.isPresent()) {
typedQuery.setMaxResults(limit.get());
}
if (offset.isPresent()) {
typedQuery.setFirstResult(offset.get());
}
return typedQuery.getResultList().stream()
.map(t -> t.toTheme(userDirectoryService))
.collect(Collectors.toList());
});
}
@Override
public Theme updateTheme(final Theme theme) throws ThemesServiceDatabaseException {
try {
Theme newTheme = db.execTxChecked(em -> {
ThemeDto themeDto = null;
if (theme.getId().isPresent()) {
themeDto = getThemeDtoQuery(theme.getId().get()).apply(em).orElse(null);
}
if (themeDto == null) {
// no theme stored, create new entity
themeDto = new ThemeDto();
themeDto.setOrganization(securityService.getOrganization().getId());
updateTheme(theme, themeDto);
em.persist(themeDto);
} else {
updateTheme(theme, themeDto);
em.merge(themeDto);
}
return themeDto.toTheme(userDirectoryService);
});
return newTheme;
} catch (Exception e) {
logger.error("Could not update theme {}", theme, e);
throw new ThemesServiceDatabaseException(e);
}
}
private void updateTheme(Theme theme, ThemeDto themeDto) {
if (theme.getId().isPresent()) {
themeDto.setId(theme.getId().get());
}
themeDto.setUsername(theme.getCreator().getUsername());
themeDto.setCreationDate(theme.getCreationDate());
themeDto.setDefault(theme.isDefault());
themeDto.setName(theme.getName());
themeDto.setDescription(theme.getDescription());
themeDto.setBumperActive(theme.isBumperActive());
themeDto.setBumperFile(theme.getBumperFile());
themeDto.setTrailerActive(theme.isTrailerActive());
themeDto.setTrailerFile(theme.getTrailerFile());
themeDto.setTitleSlideActive(theme.isTitleSlideActive());
themeDto.setTitleSlideBackground(theme.getTitleSlideBackground());
themeDto.setTitleSlideMetadata(theme.getTitleSlideMetadata());
themeDto.setLicenseSlideActive(theme.isLicenseSlideActive());
themeDto.setLicenseSlideBackground(theme.getLicenseSlideBackground());
themeDto.setLicenseSlideDescription(theme.getLicenseSlideDescription());
themeDto.setWatermarkActive(theme.isWatermarkActive());
themeDto.setWatermarkFile(theme.getWatermarkFile());
themeDto.setWatermarkPosition(theme.getWatermarkPosition());
}
@Override
public void deleteTheme(long id) throws ThemesServiceDatabaseException, NotFoundException {
try {
db.execTxChecked(em -> {
ThemeDto themeDto = getThemeDtoQuery(id).apply(em)
.orElseThrow(() -> new NotFoundException("No theme with id=" + id + " exists"));
namedQuery.remove(themeDto).accept(em);
});
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
logger.error("Could not delete theme '{}'", id, e);
throw new ThemesServiceDatabaseException(e);
}
}
@Override
public int countThemes() throws ThemesServiceDatabaseException {
try {
String orgId = securityService.getOrganization().getId();
return db.exec(namedQuery.find(
"Themes.count",
Number.class,
Pair.of("org", orgId)
)).intValue();
} catch (Exception e) {
logger.error("Could not count themes", e);
throw new ThemesServiceDatabaseException(e);
}
}
/**
* Gets a theme by its ID, using the current organizational context.
*
* @param id
* the theme identifier
* @return a query function returning an optional theme entity
*/
private Function<EntityManager, Optional<ThemeDto>> getThemeDtoQuery(long id) {
String orgId = securityService.getOrganization().getId();
return namedQuery.findOpt(
"Themes.findById",
ThemeDto.class,
Pair.of("id", id),
Pair.of("org", orgId)
);
}
}