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.themes.persistence;
23
24 import static org.opencastproject.db.Queries.namedQuery;
25
26 import org.opencastproject.db.DBSession;
27 import org.opencastproject.db.DBSessionFactory;
28 import org.opencastproject.security.api.SecurityService;
29 import org.opencastproject.security.api.UserDirectoryService;
30 import org.opencastproject.themes.Theme;
31 import org.opencastproject.themes.ThemesServiceDatabase;
32 import org.opencastproject.util.NotFoundException;
33 import org.opencastproject.util.requests.SortCriterion;
34
35 import org.apache.commons.lang3.tuple.Pair;
36 import org.osgi.service.component.ComponentContext;
37 import org.osgi.service.component.annotations.Activate;
38 import org.osgi.service.component.annotations.Component;
39 import org.osgi.service.component.annotations.Reference;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Optional;
46 import java.util.function.Function;
47 import java.util.stream.Collectors;
48
49 import javax.persistence.EntityManager;
50 import javax.persistence.EntityManagerFactory;
51 import javax.persistence.TypedQuery;
52 import javax.persistence.criteria.CriteriaBuilder;
53 import javax.persistence.criteria.CriteriaQuery;
54 import javax.persistence.criteria.Expression;
55 import javax.persistence.criteria.Order;
56 import javax.persistence.criteria.Predicate;
57 import javax.persistence.criteria.Root;
58
59
60
61
62 @Component(
63 immediate = true,
64 service = { ThemesServiceDatabase.class },
65 property = {
66 "service.description=Themes Database Service"
67 }
68 )
69 public class ThemesServiceDatabaseImpl implements ThemesServiceDatabase {
70
71 public static final String PERSISTENCE_UNIT = "org.opencastproject.themes";
72
73
74 private static final Logger logger = LoggerFactory.getLogger(ThemesServiceDatabaseImpl.class);
75
76
77 protected EntityManagerFactory emf;
78
79 protected DBSessionFactory dbSessionFactory;
80
81 protected DBSession db;
82
83
84 protected SecurityService securityService;
85
86
87 protected UserDirectoryService userDirectoryService;
88
89
90 private ComponentContext cc;
91
92
93
94
95
96
97 @Activate
98 public void activate(ComponentContext cc) {
99 logger.info("Activating persistence manager for themes");
100 this.cc = cc;
101 db = dbSessionFactory.createSession(emf);
102 }
103
104
105 @Reference(target = "(osgi.unit.name=org.opencastproject.themes)")
106 public void setEntityManagerFactory(EntityManagerFactory emf) {
107 this.emf = emf;
108 }
109
110 @Reference
111 public void setDBSessionFactory(DBSessionFactory dbSessionFactory) {
112 this.dbSessionFactory = dbSessionFactory;
113 }
114
115
116
117
118
119
120
121 @Reference
122 public void setSecurityService(SecurityService securityService) {
123 this.securityService = securityService;
124 }
125
126
127
128
129
130
131
132 @Reference
133 public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
134 this.userDirectoryService = userDirectoryService;
135 }
136
137 @Override
138 public Theme getTheme(long id) throws ThemesServiceDatabaseException, NotFoundException {
139 try {
140 return db.exec(getThemeDtoQuery(id))
141 .map(t -> t.toTheme(userDirectoryService))
142 .orElseThrow(() -> new NotFoundException("No theme with id=" + id + " exists"));
143 } catch (NotFoundException e) {
144 throw e;
145 } catch (Exception e) {
146 logger.error("Could not get theme", e);
147 throw new ThemesServiceDatabaseException(e);
148 }
149 }
150
151 private List<Theme> getThemes() throws ThemesServiceDatabaseException {
152 try {
153 String orgId = securityService.getOrganization().getId();
154 return db.exec(namedQuery.findAll(
155 "Themes.findByOrg",
156 ThemeDto.class,
157 Pair.of("org", orgId)
158 )).stream()
159 .map(t -> t.toTheme(userDirectoryService))
160 .collect(Collectors.toList());
161 } catch (Exception e) {
162 logger.error("Could not get themes", e);
163 throw new ThemesServiceDatabaseException(e);
164 }
165 }
166
167 public List<Theme> findThemes(
168 Optional<Integer> limit,
169 Optional<Integer> offset,
170 ArrayList<SortCriterion> sortCriteria,
171 Optional<String> creatorFilter,
172 Optional<String> textFilter
173 ) {
174 String orgId = securityService.getOrganization().getId();
175
176 return db.execTxChecked(em -> {
177 CriteriaBuilder cb = em.getCriteriaBuilder();
178 final CriteriaQuery<ThemeDto> query = cb.createQuery(ThemeDto.class);
179 Root<ThemeDto> theme = query.from(ThemeDto.class);
180 query.select(theme);
181 query.distinct(true);
182
183
184 List<Predicate> conditions = new ArrayList<>();
185 conditions.add(cb.equal(theme.get("organization"), orgId));
186
187
188 if (creatorFilter.isPresent()) {
189 conditions.add(cb.equal(theme.get("username"), creatorFilter.get()));
190 }
191
192 if (textFilter.isPresent()) {
193 List<Predicate> fulltextConditions = new ArrayList<>();
194 String[] tokens = textFilter.get().split("\\s+");
195 for (String token: tokens) {
196 List<Predicate> fieldConditions = new ArrayList<>();
197 Expression<String> literal = cb.literal("%" + token + "%");
198
199 fieldConditions.add(cb.like(cb.lower(theme.get("username")), cb.lower(literal)));
200 fieldConditions.add(cb.like(cb.lower(theme.get("name")), cb.lower(literal)));
201 fieldConditions.add(cb.like(cb.lower(theme.get("description")), cb.lower(literal)));
202
203
204 fulltextConditions.add(cb.or(fieldConditions.toArray(new Predicate[0])));
205 }
206
207
208 conditions.add(cb.and(fulltextConditions.toArray(new Predicate[0])));
209 }
210 query.where(cb.and(conditions.toArray(new Predicate[0])));
211
212
213 List<Order> orders = new ArrayList<>();
214 for (SortCriterion criterion : sortCriteria) {
215 String fieldName = criterion.getFieldName();
216 switch(fieldName) {
217 case "name":
218 break;
219 case "description":
220 break;
221 case "creator":
222 fieldName = "username";
223 break;
224 case "default":
225 fieldName = "isDefault";
226 break;
227 case "creation_date":
228 fieldName = "creationDate";
229 break;
230 default:
231 throw new IllegalArgumentException("Sorting criterion " + criterion.getFieldName() + " is not supported "
232 + "for themes.");
233 }
234
235 Expression expression = theme.get(fieldName);
236 if (criterion.getOrder() == SortCriterion.Order.Ascending) {
237 orders.add(cb.asc(expression));
238 } else if (criterion.getOrder() == SortCriterion.Order.Descending) {
239 orders.add(cb.desc(expression));
240 }
241
242 }
243 query.orderBy(orders);
244
245
246 TypedQuery<ThemeDto> typedQuery = em.createQuery(query);
247 if (limit.isPresent()) {
248 typedQuery.setMaxResults(limit.get());
249 }
250 if (offset.isPresent()) {
251 typedQuery.setFirstResult(offset.get());
252 }
253
254 return typedQuery.getResultList().stream()
255 .map(t -> t.toTheme(userDirectoryService))
256 .collect(Collectors.toList());
257 });
258 }
259
260 @Override
261 public Theme updateTheme(final Theme theme) throws ThemesServiceDatabaseException {
262 try {
263 Theme newTheme = db.execTxChecked(em -> {
264 ThemeDto themeDto = null;
265 if (theme.getId().isPresent()) {
266 themeDto = getThemeDtoQuery(theme.getId().get()).apply(em).orElse(null);
267 }
268
269 if (themeDto == null) {
270
271 themeDto = new ThemeDto();
272 themeDto.setOrganization(securityService.getOrganization().getId());
273 updateTheme(theme, themeDto);
274 em.persist(themeDto);
275 } else {
276 updateTheme(theme, themeDto);
277 em.merge(themeDto);
278 }
279
280 return themeDto.toTheme(userDirectoryService);
281 });
282
283 return newTheme;
284 } catch (Exception e) {
285 logger.error("Could not update theme {}", theme, e);
286 throw new ThemesServiceDatabaseException(e);
287 }
288 }
289
290 private void updateTheme(Theme theme, ThemeDto themeDto) {
291 if (theme.getId().isPresent()) {
292 themeDto.setId(theme.getId().get());
293 }
294 themeDto.setUsername(theme.getCreator().getUsername());
295 themeDto.setCreationDate(theme.getCreationDate());
296 themeDto.setDefault(theme.isDefault());
297 themeDto.setName(theme.getName());
298 themeDto.setDescription(theme.getDescription());
299 themeDto.setBumperActive(theme.isBumperActive());
300 themeDto.setBumperFile(theme.getBumperFile());
301 themeDto.setTrailerActive(theme.isTrailerActive());
302 themeDto.setTrailerFile(theme.getTrailerFile());
303 themeDto.setTitleSlideActive(theme.isTitleSlideActive());
304 themeDto.setTitleSlideBackground(theme.getTitleSlideBackground());
305 themeDto.setTitleSlideMetadata(theme.getTitleSlideMetadata());
306 themeDto.setLicenseSlideActive(theme.isLicenseSlideActive());
307 themeDto.setLicenseSlideBackground(theme.getLicenseSlideBackground());
308 themeDto.setLicenseSlideDescription(theme.getLicenseSlideDescription());
309 themeDto.setWatermarkActive(theme.isWatermarkActive());
310 themeDto.setWatermarkFile(theme.getWatermarkFile());
311 themeDto.setWatermarkPosition(theme.getWatermarkPosition());
312 }
313
314 @Override
315 public void deleteTheme(long id) throws ThemesServiceDatabaseException, NotFoundException {
316 try {
317 db.execTxChecked(em -> {
318 ThemeDto themeDto = getThemeDtoQuery(id).apply(em)
319 .orElseThrow(() -> new NotFoundException("No theme with id=" + id + " exists"));
320 namedQuery.remove(themeDto).accept(em);
321 });
322 } catch (NotFoundException e) {
323 throw e;
324 } catch (Exception e) {
325 logger.error("Could not delete theme '{}'", id, e);
326 throw new ThemesServiceDatabaseException(e);
327 }
328 }
329
330 @Override
331 public int countThemes() throws ThemesServiceDatabaseException {
332 try {
333 String orgId = securityService.getOrganization().getId();
334 return db.exec(namedQuery.find(
335 "Themes.count",
336 Number.class,
337 Pair.of("org", orgId)
338 )).intValue();
339 } catch (Exception e) {
340 logger.error("Could not count themes", e);
341 throw new ThemesServiceDatabaseException(e);
342 }
343 }
344
345
346
347
348
349
350
351
352 private Function<EntityManager, Optional<ThemeDto>> getThemeDtoQuery(long id) {
353 String orgId = securityService.getOrganization().getId();
354 return namedQuery.findOpt(
355 "Themes.findById",
356 ThemeDto.class,
357 Pair.of("id", id),
358 Pair.of("org", orgId)
359 );
360 }
361 }