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.adopter.registration;
23
24 import static org.opencastproject.db.Queries.namedQuery;
25
26 import org.opencastproject.adopter.registration.dto.Adopter;
27 import org.opencastproject.adopter.registration.dto.GeneralData;
28 import org.opencastproject.adopter.registration.dto.Host;
29 import org.opencastproject.adopter.registration.dto.StatisticData;
30 import org.opencastproject.assetmanager.api.AssetManager;
31 import org.opencastproject.capture.admin.api.CaptureAgentStateService;
32 import org.opencastproject.db.DBSession;
33 import org.opencastproject.db.DBSessionFactory;
34 import org.opencastproject.metadata.dublincore.DublinCore;
35 import org.opencastproject.metadata.dublincore.EncodingSchemeUtils;
36 import org.opencastproject.search.api.SearchResult;
37 import org.opencastproject.search.api.SearchResultList;
38 import org.opencastproject.search.api.SearchService;
39 import org.opencastproject.security.api.DefaultOrganization;
40 import org.opencastproject.security.api.Organization;
41 import org.opencastproject.security.api.OrganizationDirectoryService;
42 import org.opencastproject.security.api.SecurityService;
43 import org.opencastproject.security.api.User;
44 import org.opencastproject.security.api.UserProvider;
45 import org.opencastproject.security.util.SecurityUtil;
46 import org.opencastproject.series.api.SeriesService;
47 import org.opencastproject.serviceregistry.api.ServiceRegistry;
48 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
49 import org.opencastproject.userdirectory.JpaUserAndRoleProvider;
50 import org.opencastproject.userdirectory.JpaUserReferenceProvider;
51
52 import com.google.gson.Gson;
53
54 import org.elasticsearch.index.query.QueryBuilders;
55 import org.elasticsearch.search.builder.SearchSourceBuilder;
56 import org.osgi.framework.BundleContext;
57 import org.osgi.framework.Version;
58 import org.osgi.service.component.annotations.Activate;
59 import org.osgi.service.component.annotations.Component;
60 import org.osgi.service.component.annotations.Deactivate;
61 import org.osgi.service.component.annotations.Reference;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import java.io.IOException;
66 import java.util.Date;
67 import java.util.LinkedHashMap;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Objects;
71 import java.util.Optional;
72 import java.util.Timer;
73 import java.util.TimerTask;
74 import java.util.UUID;
75 import java.util.stream.Collectors;
76
77 import javax.persistence.EntityManagerFactory;
78 import javax.persistence.TypedQuery;
79
80
81
82
83 @Component(
84 immediate = true,
85 service = AdopterRegistrationServiceImpl.class,
86 property = {
87 "service.description=Adopter Statistics Registration Service"
88 }
89 )
90 public class AdopterRegistrationServiceImpl extends TimerTask {
91
92
93 private static final Logger logger = LoggerFactory.getLogger(AdopterRegistrationServiceImpl.class);
94
95
96 private static final String PROP_KEY_STATISTIC_SERVER_ADDRESS = "org.opencastproject.adopter.registration.server.url";
97 private static final String DEFAULT_STATISTIC_SERVER_ADDRESS = "https://register.opencast.org";
98
99 private static final int ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
100
101 private static final Gson gson = new Gson();
102
103
104
105
106
107
108 private ServiceRegistry serviceRegistry;
109
110
111 private CaptureAgentStateService caStateService;
112
113 private OrganizationDirectoryService organizationDirectoryService;
114
115
116 private AssetManager assetManager;
117
118
119 private SeriesService seriesService;
120
121
122 private SearchService searchService;
123
124
125 protected UserProvider userRefProvider;
126
127 protected JpaUserAndRoleProvider userProvider;
128
129
130 protected SecurityService securityService;
131
132
133 protected EntityManagerFactory emf = null;
134
135 protected DBSessionFactory dbSessionFactory;
136
137 protected DBSession db;
138
139
140 @Reference(target = "(osgi.unit.name=org.opencastproject.adopter.impl)")
141 public void setEntityManagerFactory(EntityManagerFactory emf) {
142 this.emf = emf;
143 }
144
145 @Reference
146 public void setDBSessionFactory(DBSessionFactory dbSessionFactory) {
147 this.dbSessionFactory = dbSessionFactory;
148 }
149
150
151
152
153
154
155
156 private AdopterRegistrationSender sender;
157
158
159 private Organization defaultOrganization;
160
161
162 private User systemAdminUser;
163
164
165 private String version;
166
167
168 private Timer timer;
169
170
171
172
173
174
175
176
177
178
179 @Activate
180 public void activate(BundleContext ctx) {
181 logger.info("Activating adopter registration service.");
182 this.defaultOrganization = new DefaultOrganization();
183 String systemAdminUserName = ctx.getProperty(SecurityUtil.PROPERTY_KEY_SYS_USER);
184 this.systemAdminUser = SecurityUtil.createSystemUser(systemAdminUserName, defaultOrganization);
185
186 final Version ctxVersion = ctx.getBundle().getVersion();
187 this.version = ctxVersion.toString();
188
189
190 final String serverBaseUrl = ctx.getProperty(PROP_KEY_STATISTIC_SERVER_ADDRESS);
191 if (serverBaseUrl != null) {
192 logger.error("\nAdopter registration information are sent to a server other than register.opencast.org.\n"
193 + "We cannot take any responsibility for what is done with the data.");
194 }
195
196 db = dbSessionFactory.createSession(emf);
197
198 this.sender = new AdopterRegistrationSender(Objects.toString(serverBaseUrl, DEFAULT_STATISTIC_SERVER_ADDRESS));
199
200
201 timer = new Timer();
202 timer.schedule(this, 0, ONE_DAY_IN_MILLISECONDS);
203 }
204
205 @Deactivate
206 public void deactivate() {
207 timer.cancel();
208 db.close();
209 }
210
211
212
213
214
215 public void save(Adopter adopter) throws AdopterRegistrationException {
216 try {
217 db.execTx(em -> {
218 Optional<Adopter> dbForm = namedQuery.findOpt("Adopter.findAll", Adopter.class).apply(em);
219 if (dbForm.isEmpty()) {
220
221 adopter.setAdopterKey(UUID.randomUUID().toString());
222 adopter.setStatisticKey(UUID.randomUUID().toString());
223 adopter.setDateCreated(new Date());
224 adopter.setDateModified(new Date());
225 em.persist(adopter);
226 } else {
227 dbForm.get().merge(adopter);
228 em.merge(dbForm.get());
229 }
230
231 });
232 } catch (Exception e) {
233 logger.error("Couldn't update the adopter statistics registration adopter: {}", e.getMessage());
234 throw new AdopterRegistrationException(e);
235 }
236 }
237
238 public void markForDeletion() {
239 Adopter a = get();
240 if (null != a) {
241 a.delete();
242 save(a);
243 }
244 }
245
246 public void delete() {
247 try {
248 db.execTx(namedQuery.delete("Adopter.deleteAll"));
249 } catch (Exception e) {
250 logger.error("Error occurred while deleting the adopter registration table. {}", e.getMessage());
251 throw new RuntimeException(e);
252 }
253 }
254
255 public Adopter get() throws AdopterRegistrationException {
256 return db.exec(namedQuery.findOpt("Adopter.findAll", Adopter.class)).orElse(null);
257 }
258
259
260
261
262
263 @Override
264 public void run() {
265 logger.info("Executing adopter statistic scheduler task.");
266
267 Adopter adopter;
268 try {
269 adopter = get();
270 if (null == adopter) {
271 logger.info("Adopter not registered, aborting");
272 return;
273 }
274 } catch (Exception e) {
275 logger.error("Couldn't retrieve adopter registration data.", e);
276 return;
277 }
278
279 if (adopter.shouldDelete()) {
280
281 Adopter f = new Adopter();
282 f.setAdopterKey(adopter.getAdopterKey());
283 GeneralData gd = new GeneralData(f);
284 gd.setAdopterKey(adopter.getAdopterKey());
285 StatisticData sd = new StatisticData(adopter.getStatisticKey());
286
287 try {
288 sender.deleteStatistics(sd.jsonify());
289 sender.deleteGeneralData(gd.jsonify());
290 markForDeletion();
291 } catch (IOException e) {
292 logger.warn("Error occurred while deleting registration data, will retry", e);
293 }
294 return;
295 }
296
297
298
299
300
301 if (adopter.isRegistered() && adopter.getTermsVersionAgreed() == Adopter.TERMSOFUSEVERSION.APRIL_2022) {
302 try {
303 String generalDataAsJson = collectGeneralData(adopter);
304 sender.sendGeneralData(generalDataAsJson);
305
306 save(adopter);
307 } catch (IOException e) {
308 logger.warn("Error occurred while processing adopter general data.", e);
309 }
310
311 if (adopter.allowsStatistics()) {
312 try {
313 StatisticData statisticData = collectStatisticData(adopter.getAdopterKey(), adopter.getStatisticKey());
314 sender.sendStatistics(statisticData.jsonify());
315 db.exec(em -> {
316 TypedQuery<AdopterRegistrationExtra> q = em.createNamedQuery("AdopterRegistrationExtra.findAll",
317 AdopterRegistrationExtra.class);
318 q.getResultList().forEach(extra -> {
319 try {
320 Map<String, Object> data = Map.of("statistic_key", statisticData.getStatisticKey(),
321 "data", gson.fromJson(extra.getData(), Map.class));
322 sender.sendExtraData(extra.getType(), gson.toJson(data));
323 } catch (IOException e) {
324 logger.warn("Unable to send extra adopter data with type '{}'", extra.getType(), e);
325 }
326 });
327 });
328
329 save(adopter);
330 } catch (IOException e) {
331 logger.warn("Unable to send adopter statistic data");
332 } catch (Exception e) {
333 logger.error("Error occurred while processing adopter statistic data.", e);
334 }
335 }
336 }
337 }
338
339 public String getRegistrationDataAsString() throws Exception {
340 Adopter adopter = get();
341 if (null == adopter) {
342 adopter = new Adopter();
343 }
344 Map<String, Object> map = new LinkedHashMap<>();
345 map.put("general", new GeneralData(adopter));
346 map.put("statistics", collectStatisticData(adopter.getAdopterKey(), adopter.getStatisticKey()));
347 db.exec(em -> {
348 TypedQuery<AdopterRegistrationExtra> q =
349 em.createNamedQuery("AdopterRegistrationExtra.findAll", AdopterRegistrationExtra.class);
350 q.getResultList().forEach(extra -> {
351 map.put(extra.getType(),gson.fromJson(extra.getData(), Map.class));
352 });
353 });
354
355 return gson.toJson(map);
356 }
357
358
359
360
361
362
363
364
365
366
367
368 private String collectGeneralData(Adopter adopterRegistrationAdopter) {
369 GeneralData generalData = new GeneralData(adopterRegistrationAdopter);
370 return generalData.jsonify();
371 }
372
373
374
375
376
377
378
379 private StatisticData collectStatisticData(String adopterKey, String statisticKey) throws Exception {
380 StatisticData statisticData = new StatisticData(statisticKey);
381 statisticData.setAdopterKey(adopterKey);
382 serviceRegistry.getHostRegistrations().forEach(host -> {
383 Host h = new Host(host);
384 try {
385 String services = serviceRegistry.getServiceRegistrationsByHost(host.getBaseUrl())
386 .stream()
387 .map(sr -> sr.getServiceType())
388 .collect(Collectors.joining(",\n"));
389 h.setServices(services);
390 } catch (ServiceRegistryException e) {
391 logger.warn("Error gathering services for {}", host.getBaseUrl(), e);
392 }
393 statisticData.addHost(h);
394 });
395 statisticData.setJobCount(serviceRegistry.count(null, null));
396
397 statisticData.setSeriesCount(seriesService.getSeriesCount());
398
399 List<Organization> orgs = organizationDirectoryService.getOrganizations();
400 statisticData.setTenantCount(orgs.size());
401
402 for (Organization org : orgs) {
403 SecurityUtil.runAs(securityService, org, systemAdminUser, () -> {
404 statisticData.setEventCount(statisticData.getEventCount() + assetManager.countEvents(org.getId()));
405
406
407 long current = statisticData.getCACount();
408 int orgCAs = caStateService.getKnownAgents().size();
409 statisticData.setCACount(current + orgCAs);
410
411 final SearchSourceBuilder q = new SearchSourceBuilder().query(
412 QueryBuilders.boolQuery()
413 .must(QueryBuilders.termQuery(SearchResult.TYPE, SearchService.IndexEntryType.Episode))
414 .must(QueryBuilders.termQuery(SearchResult.ORG, org.getId()))
415 .mustNot(QueryBuilders.existsQuery(SearchResult.DELETED_DATE)));
416 final SearchResultList results = searchService.search(q);
417 long orgMilis = results.getHits().stream().map(
418 result -> EncodingSchemeUtils.decodeDuration(Objects.toString(
419 result.getDublinCore().getFirst(DublinCore.PROPERTY_EXTENT),
420 "0")))
421 .filter(Objects::nonNull)
422 .reduce(Long::sum).orElse(0L);
423 statisticData.setTotalMinutes(statisticData.getTotalMinutes() + (orgMilis / 1000 / 60));
424
425
426 long currentUsers = statisticData.getUserCount();
427 statisticData.setUserCount(currentUsers + userProvider.countUsers() + userRefProvider.countUsers());
428 });
429 }
430 statisticData.setVersion(version);
431 return statisticData;
432 }
433
434
435
436
437
438
439
440 @Reference
441 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
442 this.serviceRegistry = serviceRegistry;
443 }
444
445 @Reference
446 public void setCaptureAdminService(CaptureAgentStateService stateService) {
447 this.caStateService = stateService;
448 }
449
450
451 @Reference
452 public void setAssetManager(AssetManager assetManager) {
453 this.assetManager = assetManager;
454 }
455
456
457 @Reference
458 public void setSeriesService(SeriesService seriesService) {
459 this.seriesService = seriesService;
460 }
461
462 @Reference
463 public void setSearchService(SearchService searchService) {
464 this.searchService = searchService;
465 }
466
467
468 @Reference
469 public void setUserRefProvider(JpaUserReferenceProvider userRefProvider) {
470 this.userRefProvider = userRefProvider;
471 }
472
473
474 @Reference
475 public void setUserAndRoleProvider(JpaUserAndRoleProvider userProvider) {
476 this.userProvider = userProvider;
477 }
478
479
480 @Reference
481 public void setSecurityService(SecurityService securityService) {
482 this.securityService = securityService;
483 }
484
485
486 @Reference
487 public void setOrganizationDirectoryService(OrganizationDirectoryService orgDirServ) {
488 this.organizationDirectoryService = orgDirServ;
489 }
490 }