View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.event.handler;
22  
23  import static org.opencastproject.util.OsgiUtil.getOptCfg;
24  import static org.opencastproject.util.OsgiUtil.getOptCfgAsBoolean;
25  
26  import org.opencastproject.assetmanager.api.AssetManager;
27  import org.opencastproject.assetmanager.api.Snapshot;
28  import org.opencastproject.assetmanager.api.query.AQueryBuilder;
29  import org.opencastproject.assetmanager.api.query.ARecord;
30  import org.opencastproject.assetmanager.api.query.AResult;
31  import org.opencastproject.mediapackage.MediaPackage;
32  import org.opencastproject.mediapackage.MediaPackageElement;
33  import org.opencastproject.mediapackage.selector.SimpleElementSelector;
34  import org.opencastproject.message.broker.api.assetmanager.AssetManagerItem;
35  import org.opencastproject.oaipmh.persistence.OaiPmhDatabase;
36  import org.opencastproject.oaipmh.persistence.QueryBuilder;
37  import org.opencastproject.oaipmh.persistence.SearchResult;
38  import org.opencastproject.oaipmh.persistence.SearchResultItem;
39  import org.opencastproject.publication.api.OaiPmhPublicationService;
40  import org.opencastproject.security.api.Organization;
41  import org.opencastproject.security.api.SecurityService;
42  import org.opencastproject.security.api.User;
43  import org.opencastproject.security.util.SecurityUtil;
44  import org.opencastproject.util.data.Collections;
45  import org.opencastproject.util.data.Option;
46  
47  import org.osgi.framework.BundleContext;
48  import org.osgi.service.cm.ConfigurationException;
49  import org.osgi.service.cm.ManagedService;
50  import org.osgi.service.component.annotations.Activate;
51  import org.osgi.service.component.annotations.Component;
52  import org.osgi.service.component.annotations.Reference;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import java.util.Collection;
57  import java.util.Dictionary;
58  import java.util.HashSet;
59  import java.util.Optional;
60  import java.util.Set;
61  
62  @Component(
63      immediate = true,
64      service = {
65          ManagedService.class,
66          OaiPmhUpdatedEventHandler.class
67      },
68      property = {
69          "service.description=OAI-PMH Updated Event Handler",
70          "service.pid=org.opencastproject.event.handler.OaiPmhUpdatedEventHandler"
71      }
72  )
73  public class OaiPmhUpdatedEventHandler implements ManagedService {
74  
75    /** The logger */
76    protected static final Logger logger = LoggerFactory.getLogger(OaiPmhUpdatedEventHandler.class);
77  
78    // config keys
79    protected static final String CFG_PROPAGATE_EPISODE = "propagate.episode";
80    protected static final String CFG_FLAVORS = "flavors";
81    protected static final String CFG_TAGS = "tags";
82  
83    /** Whether to propagate episode meta data changes to OAI-PMH or not */
84    private boolean propagateEpisode = false;
85  
86    /** List of flavors to redistribute */
87    private Set<String> flavors = new HashSet<>();
88  
89    /** List of tags to redistribute */
90    private Set<String> tags = new HashSet<>();
91  
92    /** The security service */
93    private SecurityService securityService = null;
94  
95    /** The OAI-PMH database */
96    private OaiPmhDatabase oaiPmhPersistence = null;
97  
98    /** The OAI-PMH publication service */
99    private OaiPmhPublicationService oaiPmhPublicationService = null;
100 
101   /** The system account to use for running asynchronous events */
102   protected String systemAccount = null;
103 
104   /** The asset manager */
105   protected AssetManager assetManager = null;
106 
107   /**
108    * OSGI callback for component activation.
109    *
110    * @param bundleContext
111    *          the OSGI bundle context
112    */
113   @Activate
114   protected void activate(BundleContext bundleContext) {
115     this.systemAccount = bundleContext.getProperty("org.opencastproject.security.digest.user");
116   }
117 
118   @Override
119   public void updated(Dictionary<String, ?> dictionary) throws ConfigurationException {
120     final Option<Boolean> propagateEpisode = getOptCfgAsBoolean(dictionary, CFG_PROPAGATE_EPISODE);
121     if (propagateEpisode.isSome()) {
122       this.propagateEpisode = propagateEpisode.get();
123     }
124 
125     final Option<String> flavorsRaw = getOptCfg(dictionary, CFG_FLAVORS);
126     if (flavorsRaw.isSome()) {
127       final String[] flavorStrings = flavorsRaw.get().split("\\s*,\\s*");
128       this.flavors = Collections.set(flavorStrings);
129     } else {
130       this.flavors = new HashSet<>();
131     }
132 
133     final Option<String> tagsRaw = getOptCfg(dictionary, CFG_TAGS);
134     if (tagsRaw.isSome()) {
135       final String[] tags = tagsRaw.get().split("\\s*,\\s*");
136       this.tags = Collections.set(tags);
137     } else {
138       this.tags = new HashSet<>();
139     }
140   }
141 
142   public void handleEvent(AssetManagerItem.TakeSnapshot snapshotItem) {
143     if (!propagateEpisode) {
144       logger.trace("Skipping automatic propagation of episode meta data to OAI-PMH since it is turned off.");
145       return;
146     }
147 
148     //An episode or its ACL has been updated. Construct the MediaPackage and publish it to OAI-PMH.
149     logger.debug("Handling update event for media package {}", snapshotItem.getId());
150 
151     // We must be an administrative user to make a query to the OaiPmhPublicationService
152     final User prevUser = securityService.getUser();
153     final Organization prevOrg = securityService.getOrganization();
154 
155     try {
156       securityService.setUser(SecurityUtil.createSystemUser(systemAccount, prevOrg));
157 
158       // The mediapackage from TakeSnapshot is in some cases from a workflow instance,
159       // that has already been finished. The URLs may have become stale.
160       // For that reason we get the mediapackage from the asset manager.
161       String versionStr = Long.toString(snapshotItem.getVersion());
162       AQueryBuilder q = assetManager.createQuery();
163       AResult snapshotQueryResult = q.select(q.snapshot())
164               .where(q.organizationId().eq(prevOrg.getId())
165                     .and(q.mediaPackageId(snapshotItem.getId())
166                     .and(q.version().eq(assetManager.toVersion(versionStr).get())))).run();
167       Optional<ARecord> snapshotRecordOpt = snapshotQueryResult.getRecords().stream().findFirst();
168       if (snapshotRecordOpt.isPresent()) {
169         Snapshot snapshot = snapshotRecordOpt.get().getSnapshot().get();
170         MediaPackage snapshotMp = snapshot.getMediaPackage();
171 
172         // Check weather the media package contains elements to republish
173         SimpleElementSelector mpeSelector = new SimpleElementSelector();
174         for (String flavor : flavors) {
175           mpeSelector.addFlavor(flavor);
176         }
177         for (String tag : tags) {
178           mpeSelector.addTag(tag);
179         }
180         Collection<MediaPackageElement> elementsToUpdate = mpeSelector.select(snapshotMp, true);
181         if (elementsToUpdate == null || elementsToUpdate.isEmpty()) {
182           logger.debug("The media package {} does not contain any elements matching the given flavors and tags",
183                   snapshotMp.getIdentifier().toString());
184           return;
185         }
186 
187         SearchResult result = oaiPmhPersistence.search(
188                 QueryBuilder.query().mediaPackageId(snapshotMp).isDeleted(false).build());
189         for (SearchResultItem searchResultItem : result.getItems()) {
190           try {
191             oaiPmhPublicationService.updateMetadata(snapshotMp, searchResultItem.getRepository(), flavors, tags, false);
192             // we don't want to wait for job completion here because it will block the message queue
193           } catch (Exception e) {
194             logger.error("Unable to update OAI-PMH publication for the media package {} in repository {}",
195                     snapshotItem.getId(), searchResultItem.getRepository(), e);
196           }
197         }
198       }
199     } finally {
200       securityService.setOrganization(prevOrg);
201       securityService.setUser(prevUser);
202     }
203   }
204 
205   @Reference
206   public void setAssetManager(AssetManager assetManager) {
207     this.assetManager = assetManager;
208   }
209 
210   @Reference
211   public void setOaiPmhPersistence(OaiPmhDatabase oaiPmhPersistence) {
212     this.oaiPmhPersistence = oaiPmhPersistence;
213   }
214 
215   @Reference
216   public void setOaiPmhPublicationService(OaiPmhPublicationService oaiPmhPublicationService) {
217     this.oaiPmhPublicationService = oaiPmhPublicationService;
218   }
219 
220   @Reference
221   public void setSecurityService(SecurityService securityService) {
222     this.securityService = securityService;
223   }
224 }