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.elasticsearch.index.objects.event;
23
24 import static org.opencastproject.security.util.SecurityUtil.getEpisodeRoleId;
25
26 import org.opencastproject.elasticsearch.api.SearchIndexException;
27 import org.opencastproject.elasticsearch.api.SearchMetadata;
28 import org.opencastproject.elasticsearch.api.SearchResult;
29 import org.opencastproject.elasticsearch.impl.SearchMetadataCollection;
30 import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
31 import org.opencastproject.elasticsearch.index.objects.series.Series;
32 import org.opencastproject.elasticsearch.index.objects.series.SeriesIndexSchema;
33 import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
34 import org.opencastproject.list.api.ListProviderException;
35 import org.opencastproject.list.api.ListProvidersService;
36 import org.opencastproject.list.api.ResourceListQuery;
37 import org.opencastproject.list.impl.ResourceListQueryImpl;
38 import org.opencastproject.mediapackage.Attachment;
39 import org.opencastproject.mediapackage.Catalog;
40 import org.opencastproject.mediapackage.EName;
41 import org.opencastproject.mediapackage.MediaPackage;
42 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
43 import org.opencastproject.mediapackage.Publication;
44 import org.opencastproject.mediapackage.Track;
45 import org.opencastproject.metadata.dublincore.DCMIPeriod;
46 import org.opencastproject.metadata.dublincore.DublinCore;
47 import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
48 import org.opencastproject.metadata.dublincore.EncodingSchemeUtils;
49 import org.opencastproject.security.api.AccessControlEntry;
50 import org.opencastproject.security.api.AccessControlList;
51 import org.opencastproject.security.api.AccessControlParser;
52 import org.opencastproject.security.api.Permissions;
53 import org.opencastproject.security.api.Permissions.Action;
54 import org.opencastproject.security.api.User;
55 import org.opencastproject.util.DateTimeSupport;
56
57 import org.apache.commons.io.IOUtils;
58 import org.apache.commons.lang3.StringUtils;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import java.io.IOException;
63 import java.nio.charset.Charset;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.Date;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Set;
72 import java.util.TreeSet;
73
74 import javax.xml.bind.Unmarshaller;
75
76
77
78
79 public final class EventIndexUtils {
80
81 private static final Logger logger = LoggerFactory.getLogger(EventIndexUtils.class);
82
83
84 public static final int DEFAULT_ATTEMPTS = 10;
85
86 public static final long DEFAULT_SLEEP = 100L;
87
88
89
90
91 private EventIndexUtils() {
92 }
93
94
95
96
97
98
99
100
101
102
103
104 public static Event toRecordingEvent(SearchMetadataCollection metadata, Unmarshaller unmarshaller)
105 throws IOException {
106 Map<String, SearchMetadata<?>> metadataMap = metadata.toMap();
107 String eventJson = (String) metadataMap.get(EventIndexSchema.OBJECT).getValue();
108 return Event.valueOf(IOUtils.toInputStream(eventJson, Charset.defaultCharset()), unmarshaller);
109 }
110
111
112
113
114
115
116
117
118 public static SearchMetadataCollection toSearchMetadata(Event event, ListProvidersService listProviderService) {
119 SearchMetadataCollection metadata = new SearchMetadataCollection(
120 event.getIdentifier().concat(event.getOrganization()), Event.DOCUMENT_TYPE);
121 metadata.addField(EventIndexSchema.UID, event.getIdentifier(), false);
122 metadata.addField(EventIndexSchema.ORGANIZATION, event.getOrganization(), false);
123 metadata.addField(EventIndexSchema.OBJECT, event.toXML(), false);
124 if (StringUtils.isNotBlank(event.getTitle())) {
125 metadata.addField(EventIndexSchema.TITLE, event.getTitle(), true);
126 }
127 if (StringUtils.isNotBlank(event.getDescription())) {
128 metadata.addField(EventIndexSchema.DESCRIPTION, event.getDescription(), true);
129 }
130 if (StringUtils.isNotBlank(event.getLocation())) {
131 metadata.addField(EventIndexSchema.LOCATION, event.getLocation(), true);
132 }
133 if (StringUtils.isNotBlank(event.getSeriesId())) {
134 metadata.addField(EventIndexSchema.SERIES_ID, event.getSeriesId(), false);
135 }
136 if (StringUtils.isNotBlank(event.getSeriesName())) {
137 metadata.addField(EventIndexSchema.SERIES_NAME, event.getSeriesName(), true);
138 }
139 if (StringUtils.isNotBlank(event.getLanguage())) {
140 metadata.addField(EventIndexSchema.LANGUAGE, event.getLanguage(), false);
141 }
142 if (StringUtils.isNotBlank(event.getSubject())) {
143 metadata.addField(EventIndexSchema.SUBJECT, event.getSubject(), true);
144 }
145 if (StringUtils.isNotBlank(event.getSource())) {
146 metadata.addField(EventIndexSchema.SOURCE, event.getSource(), false);
147 }
148 if (StringUtils.isNotBlank(event.getCreated())) {
149 metadata.addField(EventIndexSchema.CREATED, event.getCreated(), false);
150 }
151 if (StringUtils.isNotBlank(event.getCreator())) {
152 metadata.addField(EventIndexSchema.CREATOR, event.getCreator(), true);
153 }
154 if (StringUtils.isNotBlank(event.getPublisher())) {
155 metadata.addField(EventIndexSchema.PUBLISHER, event.getPublisher(), true);
156 }
157 if (StringUtils.isNotBlank(event.getLicense())) {
158 metadata.addField(EventIndexSchema.LICENSE, event.getLicense(), false);
159 }
160 if (StringUtils.isNotBlank(event.getRights())) {
161 metadata.addField(EventIndexSchema.RIGHTS, event.getRights(), true);
162 }
163 if (StringUtils.isNotBlank(event.getManagedAcl())) {
164 metadata.addField(EventIndexSchema.MANAGED_ACL, event.getManagedAcl(), false);
165 }
166 if (StringUtils.isNotBlank(event.getWorkflowState())) {
167 metadata.addField(EventIndexSchema.WORKFLOW_STATE, event.getWorkflowState(), false);
168 }
169 if (event.getWorkflowId() != null) {
170 metadata.addField(EventIndexSchema.WORKFLOW_ID, event.getWorkflowId(), false);
171 }
172 if (StringUtils.isNotBlank(event.getWorkflowDefinitionId())) {
173 metadata.addField(EventIndexSchema.WORKFLOW_DEFINITION_ID, event.getWorkflowDefinitionId(), false);
174 }
175 if (StringUtils.isNotBlank(event.getRecordingStartDate())) {
176 metadata.addField(EventIndexSchema.START_DATE, event.getRecordingStartDate(), false);
177 }
178 if (StringUtils.isNotBlank(event.getRecordingEndDate())) {
179 metadata.addField(EventIndexSchema.END_DATE, event.getRecordingEndDate(), false);
180 }
181 if (event.getDuration() != null) {
182 metadata.addField(EventIndexSchema.DURATION, event.getDuration(), false);
183 }
184 if (event.getArchiveVersion() != null) {
185 metadata.addField(EventIndexSchema.ARCHIVE_VERSION, event.getArchiveVersion(), false);
186 }
187 if (event.getRecordingStatus() != null) {
188 metadata.addField(EventIndexSchema.RECORDING_STATUS, event.getRecordingStatus(), false);
189 }
190
191 metadata.addField(EventIndexSchema.EVENT_STATUS, event.getEventStatus(), false);
192
193 metadata.addField(EventIndexSchema.HAS_COMMENTS, event.hasComments(), false);
194 metadata.addField(EventIndexSchema.HAS_OPEN_COMMENTS, event.hasOpenComments(), false);
195
196 if (event.comments() != null) {
197 List<Comment> comments = event.comments();
198 HashMap<String, Object>[] commentsArray = new HashMap[comments.size()];
199 for (int i = 0; i < comments.size(); i++) {
200 Comment comment = comments.get(i);
201 HashMap<String, Object> myMap = new HashMap<String, Object>() {{
202 put(CommentIndexSchema.ID, comment.getId());
203 put(CommentIndexSchema.REASON, comment.getReason());
204 put(CommentIndexSchema.TEXT, comment.getText());
205 put(CommentIndexSchema.RESOLVED_STATUS, comment.isResolvedStatus());
206 }};
207 commentsArray[i] = myMap;
208
209 }
210
211 metadata.addField(EventIndexSchema.COMMENTS, commentsArray, false);
212 }
213
214 metadata.addField(EventIndexSchema.NEEDS_CUTTING, event.needsCutting(), false);
215
216 if (event.getPublications() != null) {
217 List<Publication> publications = event.getPublications();
218 HashMap<String, Object>[] publicationsArray = new HashMap[publications.size()];
219 for (int i = 0; i < publications.size(); i++) {
220 publicationsArray[i] = generatePublicationDoc(publications.get(i));
221 }
222
223 if (publications.size() == 1 && !publications.get(0).getChannel().equals("internal") || publications.size() > 1) {
224 metadata.addField(EventIndexSchema.IS_PUBLISHED, true, false);
225 } else {
226 metadata.addField(EventIndexSchema.IS_PUBLISHED, false, false);
227 }
228
229 metadata.addField(EventIndexSchema.PUBLICATION, publicationsArray, false);
230
231 }
232
233 if (event.getPresenters() != null) {
234 List<String> presenters = event.getPresenters();
235 metadata.addField(EventIndexSchema.PRESENTER, presenters.toArray(new String[presenters.size()]), true);
236 }
237
238 if (event.getContributors() != null) {
239 List<String> contributors = event.getContributors();
240 metadata.addField(EventIndexSchema.CONTRIBUTOR, contributors.toArray(new String[contributors.size()]), true);
241 }
242
243 if (!event.getExtendedMetadata().isEmpty()) {
244 addExtendedMetadata(metadata, event.getExtendedMetadata());
245 }
246
247 if (StringUtils.isNotBlank(event.getAccessPolicy())) {
248 metadata.addField(EventIndexSchema.ACCESS_POLICY, event.getAccessPolicy(), false);
249 addAuthorization(metadata, event.getAccessPolicy(), event.getIdentifier(), listProviderService);
250 }
251
252 if (StringUtils.isNotBlank(event.getAgentId())) {
253 metadata.addField(EventIndexSchema.AGENT_ID, event.getAgentId(), false);
254 }
255
256 if (StringUtils.isNotBlank(event.getTechnicalStartTime())) {
257 metadata.addField(EventIndexSchema.TECHNICAL_START, event.getTechnicalStartTime(), false);
258 }
259
260 if (StringUtils.isNotBlank(event.getTechnicalEndTime())) {
261 metadata.addField(EventIndexSchema.TECHNICAL_END, event.getTechnicalEndTime(), false);
262 }
263
264 if (event.getTechnicalPresenters() != null) {
265 metadata.addField(EventIndexSchema.TECHNICAL_PRESENTERS,
266 event.getTechnicalPresenters().toArray(new String[event.getTechnicalPresenters().size()]), false);
267 }
268
269 return metadata;
270 }
271
272 private static void addObjectStringToMap(HashMap<String, Object> map, String key, Object value) {
273 if (value == null) {
274 map.put(key, "");
275 } else {
276 map.put(key, value.toString());
277 }
278 }
279
280
281
282
283
284
285
286
287 private static HashMap<String, Object> generatePublicationDoc(Publication publication) {
288 HashMap<String, Object> pMap = new HashMap<String, Object>();
289
290
291 pMap.put(PublicationIndexSchema.CHANNEL, publication.getChannel());
292 addObjectStringToMap(pMap, PublicationIndexSchema.MIMETYPE, publication.getMimeType());
293
294
295 Attachment[] attachments = publication.getAttachments();
296 HashMap<String, Object>[] attachmentsArray = new HashMap[attachments.length];
297 for (int i = 0; i < attachmentsArray.length; i++) {
298 Attachment attachment = attachments[i];
299 HashMap<String, Object> element = new HashMap<String, Object>();
300 element.put(PublicationIndexSchema.ELEMENT_ID, attachment.getIdentifier());
301 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, attachment.getMimeType());
302 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, attachment.getElementType());
303 element.put(PublicationIndexSchema.ELEMENT_TAG, attachment.getTags());
304 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, attachment.getURI());
305 element.put(PublicationIndexSchema.ELEMENT_SIZE, attachment.getSize());
306 attachmentsArray[i] = element;
307 }
308 pMap.put(PublicationIndexSchema.ATTACHMENT, attachmentsArray);
309
310
311 Catalog[] catalogs = publication.getCatalogs();
312 HashMap<String, Object>[] catalogsArray = new HashMap[catalogs.length];
313 for (int i = 0; i < catalogsArray.length; i++) {
314 Catalog catalog = catalogs[i];
315 HashMap<String, Object> element = new HashMap<String, Object>();
316 element.put(PublicationIndexSchema.ELEMENT_ID, catalog.getIdentifier());
317 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, catalog.getMimeType());
318 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, catalog.getElementType());
319 element.put(PublicationIndexSchema.ELEMENT_TAG, catalog.getTags());
320 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, catalog.getURI());
321 element.put(PublicationIndexSchema.ELEMENT_SIZE, catalog.getSize());
322 catalogsArray[i] = element;
323 }
324 pMap.put(PublicationIndexSchema.CATALOG, catalogsArray);
325
326
327 Track[] tracks = publication.getTracks();
328 HashMap<String, Object>[] tracksArray = new HashMap[tracks.length];
329 for (int i = 0; i < tracksArray.length; i++) {
330 Track track = tracks[i];
331 HashMap<String, Object> element = new HashMap<String, Object>();
332 element.put(PublicationIndexSchema.ELEMENT_ID, track.getIdentifier());
333 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, track.getMimeType());
334 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, track.getElementType());
335 element.put(PublicationIndexSchema.ELEMENT_TAG, track.getTags());
336 addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, track.getURI());
337 element.put(PublicationIndexSchema.ELEMENT_SIZE, track.getSize());
338 element.put(PublicationIndexSchema.TRACK_DURATION, track.getDuration());
339 tracksArray[i] = element;
340 }
341 pMap.put(PublicationIndexSchema.TRACK, tracksArray);
342
343 return pMap;
344 }
345
346
347
348
349
350
351
352
353
354 private static void addExtendedMetadata(SearchMetadataCollection doc, Map<String, Map<String,
355 List<String>>> extendedMetadata) {
356 for (String type: extendedMetadata.keySet()) {
357 Map<String, List<String>> extendedMetadataByType = extendedMetadata.get(type);
358 for (String name: extendedMetadataByType.keySet()) {
359 List<String> values = extendedMetadataByType.get(name);
360 String fieldName = SeriesIndexSchema.EXTENDED_METADATA_PREFIX.concat(type + "_" + name);
361 doc.addField(fieldName, values, true);
362 }
363 }
364 }
365
366
367
368
369
370
371
372
373
374 private static void addAuthorization(SearchMetadataCollection doc, String aclString,
375 String eventId, ListProvidersService listProvidersService) {
376 Map<String, List<String>> permissions = new HashMap<>();
377
378
379 for (Action action : Permissions.Action.values()) {
380 permissions.put(action.toString(), new ArrayList<>());
381 }
382
383
384 AccessControlList acl = AccessControlParser.parseAclSilent(aclString);
385 List<AccessControlEntry> entries = acl.getEntries();
386
387
388 Set<AccessControlEntry> customEntries = new HashSet<>();
389 customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, "READ"), "read", true));
390 customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, "WRITE"), "write", true));
391
392 ResourceListQuery query = new ResourceListQueryImpl();
393 if (listProvidersService.hasProvider("ACL.ACTIONS")) {
394 Map<String, String> actions = new HashMap<>();
395 try {
396 actions = listProvidersService.getList("ACL.ACTIONS", query, true);
397 } catch (ListProviderException e) {
398 logger.error("Listproviders not loaded. " + e);
399 }
400 for (String action : actions.keySet()) {
401 customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, action), action, true));
402 }
403 }
404
405 entries.addAll(customEntries);
406
407
408 for (AccessControlEntry entry : entries) {
409 if (!entry.isAllow()) {
410 logger.info("Event index does not support denial via ACL, ignoring {}", entry);
411 continue;
412 }
413 List<String> actionPermissions = permissions.get(entry.getAction());
414 if (actionPermissions == null) {
415 actionPermissions = new ArrayList<>();
416 permissions.put(entry.getAction(), actionPermissions);
417 }
418 actionPermissions.add(entry.getRole());
419 }
420
421
422 for (Map.Entry<String, List<String>> entry : permissions.entrySet()) {
423 String fieldName = EventIndexSchema.ACL_PERMISSION_PREFIX.concat(entry.getKey());
424 doc.addField(fieldName, entry.getValue(), false);
425 }
426 }
427
428
429
430
431
432
433
434
435
436
437
438
439
440 public static Event updateEventExtendedMetadata(Event event, DublinCoreCatalog dc, MediaPackageElementFlavor flavor) {
441 Map<String, List<String>> map = new HashMap();
442 Set<EName> eNames = dc.getProperties();
443 for (EName eName: eNames) {
444 String name = eName.getLocalName();
445 List<String> values = dc.get(eName, DublinCore.LANGUAGE_ANY);
446 map.put(name, values);
447 }
448 event.setExtendedMetadata(flavor.toString(), map);
449 return event;
450 }
451
452
453
454
455
456
457
458
459
460
461 public static Event updateEvent(Event event, DublinCore dc) {
462 event.setTitle(dc.getFirst(DublinCore.PROPERTY_TITLE));
463 event.setDescription(dc.getFirst(DublinCore.PROPERTY_DESCRIPTION));
464 event.setSubject(dc.getFirst(DublinCore.PROPERTY_SUBJECT));
465 event.setLocation(dc.getFirst(DublinCore.PROPERTY_SPATIAL));
466 event.setLanguage(dc.getFirst(DublinCore.PROPERTY_LANGUAGE));
467 event.setSource(dc.getFirst(DublinCore.PROPERTY_SOURCE));
468 event.setSeriesId(dc.getFirst(DublinCore.PROPERTY_IS_PART_OF));
469 event.setLicense(dc.getFirst(DublinCore.PROPERTY_LICENSE));
470 event.setRights(dc.getFirst(DublinCore.PROPERTY_RIGHTS_HOLDER));
471 event.setPublisher(dc.getFirst(DublinCore.PROPERTY_PUBLISHER));
472 Date created;
473 String encodedDate = dc.getFirst(DublinCore.PROPERTY_CREATED);
474 if (StringUtils.isBlank(encodedDate)) {
475 created = new Date();
476 } else {
477 created = EncodingSchemeUtils.decodeDate(encodedDate);
478 }
479 event.setCreated(DateTimeSupport.toUTC(created.getTime()));
480 String strPeriod = dc.getFirst(DublinCore.PROPERTY_TEMPORAL);
481 try {
482 if (StringUtils.isNotBlank(strPeriod)) {
483 DCMIPeriod period = EncodingSchemeUtils.decodeMandatoryPeriod(strPeriod);
484 event.setRecordingStartDate(DateTimeSupport.toUTC(period.getStart().getTime()));
485 event.setRecordingEndDate(DateTimeSupport.toUTC(period.getEnd().getTime()));
486 event.setDuration(period.getEnd().getTime() - period.getStart().getTime());
487 } else {
488 event.setRecordingStartDate(DateTimeSupport.toUTC(created.getTime()));
489 }
490 } catch (Exception e) {
491 logger.warn("Invalid start and end date/time for event {}: {}", event.getIdentifier(), strPeriod);
492 event.setRecordingStartDate(DateTimeSupport.toUTC(created.getTime()));
493 }
494
495 updateTechnicalDate(event);
496
497
498 event.setContributors(dc.get(DublinCore.PROPERTY_CONTRIBUTOR, DublinCore.LANGUAGE_ANY));
499 event.setPresenters(dc.get(DublinCore.PROPERTY_CREATOR, DublinCore.LANGUAGE_ANY));
500 return event;
501 }
502
503 public static Event updateTechnicalDate(Event event) {
504 if (event.isScheduledEvent() && event.hasRecordingStarted()) {
505
506 event.setTechnicalStartTime(event.getRecordingStartDate());
507 event.setTechnicalEndTime(event.getRecordingEndDate());
508 } else {
509
510 if (StringUtils.isBlank(event.getTechnicalStartTime())) {
511 event.setTechnicalStartTime(event.getRecordingStartDate());
512 }
513 if (StringUtils.isBlank(event.getTechnicalEndTime())) {
514 event.setTechnicalEndTime(event.getRecordingEndDate());
515 }
516 }
517 return event;
518 }
519
520
521
522
523
524
525
526
527
528
529 public static Event updateEvent(Event event, MediaPackage mp) {
530 event.setPublications(Arrays.asList(mp.getPublications()));
531 event.setSeriesName(mp.getSeriesTitle());
532 return event;
533 }
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548 public static void updateSeriesName(Event event, String organization, User user, ElasticsearchIndex searchIndex)
549 throws SearchIndexException {
550 updateSeriesName(event, organization, user, searchIndex, DEFAULT_ATTEMPTS, DEFAULT_SLEEP);
551 }
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569 public static void updateSeriesName(Event event, String organization, User user, ElasticsearchIndex searchIndex,
570 int tries, long sleep) throws SearchIndexException {
571 if (event.getSeriesId() != null) {
572 for (int i = 1; i <= tries; i++) {
573 SearchResult<Series> result = searchIndex.getByQuery(
574 new SeriesSearchQuery(organization, user).withoutActions().withIdentifier(event.getSeriesId()));
575 if (result.getHitCount() > 0) {
576 event.setSeriesName(result.getItems()[0].getSource().getTitle());
577 break;
578 } else {
579 Integer triesLeft = tries - i;
580 logger.debug("Not able to find the series {} in the search index for the event {}. Will try {} more times.",
581 event.getSeriesId(), event.getIdentifier(), triesLeft);
582 try {
583 Thread.sleep(sleep);
584 } catch (InterruptedException e) {
585 logger.warn("Interrupted while sleeping before checking for the series being added to the index", e);
586 }
587 }
588
589 }
590 }
591 }
592
593
594
595
596
597
598
599
600 private static String[] getPublicationFlavors(List<Publication> publications) {
601 Set<String> allPublicationFlavors = new TreeSet<String>();
602 for (Publication p : publications) {
603 for (Attachment attachment : p.getAttachments()) {
604 if (attachment.getFlavor() != null) {
605 allPublicationFlavors.add(attachment.getFlavor().toString());
606 }
607 }
608 for (Catalog catalog : p.getCatalogs()) {
609 if (catalog.getFlavor() != null) {
610 allPublicationFlavors.add(catalog.getFlavor().toString());
611 }
612 }
613 for (Track track : p.getTracks()) {
614 if (track.getFlavor() != null) {
615 allPublicationFlavors.add(track.getFlavor().toString());
616 }
617 }
618 }
619 return allPublicationFlavors.toArray(new String[allPublicationFlavors.size()]);
620 }
621
622
623
624
625
626
627
628
629 public static Boolean subflavorMatches(List<Publication> publications, String previewSubtype) {
630 String[] publicationFlavors = getPublicationFlavors(publications);
631 if (publicationFlavors != null && previewSubtype != null) {
632 final String subtype = "/" + previewSubtype;
633 for (String flavor : publicationFlavors) {
634 if (flavor.endsWith(subtype)) {
635 return true;
636 }
637 }
638 }
639 return false;
640 }
641
642 }