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  
22  package org.opencastproject.assetmanager.impl;
23  
24  import static org.opencastproject.util.data.Option.some;
25  
26  import org.opencastproject.assetmanager.api.AssetManager;
27  import org.opencastproject.kernel.scanner.AbstractScanner;
28  import org.opencastproject.security.api.Organization;
29  import org.opencastproject.security.api.OrganizationDirectoryService;
30  import org.opencastproject.security.api.SecurityService;
31  import org.opencastproject.serviceregistry.api.ServiceRegistry;
32  import org.opencastproject.util.NeedleEye;
33  import org.opencastproject.util.NotFoundException;
34  import org.opencastproject.workflow.api.WorkflowService;
35  
36  import org.apache.commons.lang3.BooleanUtils;
37  import org.apache.commons.lang3.StringUtils;
38  import org.osgi.service.cm.ConfigurationException;
39  import org.osgi.service.cm.ManagedService;
40  import org.osgi.service.component.ComponentContext;
41  import org.osgi.service.component.annotations.Activate;
42  import org.osgi.service.component.annotations.Component;
43  import org.osgi.service.component.annotations.Deactivate;
44  import org.osgi.service.component.annotations.Reference;
45  import org.quartz.CronExpression;
46  import org.quartz.JobDetail;
47  import org.quartz.JobExecutionContext;
48  import org.quartz.impl.StdSchedulerFactory;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  import java.util.Calendar;
53  import java.util.Date;
54  import java.util.Dictionary;
55  import java.util.concurrent.TimeUnit;
56  
57  @Component(
58      immediate = true,
59      service = ManagedService.class,
60      property = {
61          "service.description=Timed media archiver Service"
62      }
63  )
64  public class TimedMediaArchiver extends AbstractScanner implements ManagedService {
65    private static final Logger logger = LoggerFactory.getLogger(TimedMediaArchiver.class);
66  
67    public static final String PARAM_KEY_STORE_ID = "store-id";
68    public static final String PARAM_KEY_MAX_AGE = "max-age";
69    public static final String JOB_GROUP = "oc-asset-manager-timed-media-archiver-group";
70    public static final String JOB_NAME = "oc-asset-manager-timed-media-archive-job";
71    public static final String SCANNER_NAME = "Timed media archive offloader";
72    public static final String TRIGGER_GROUP = "oc-asset-manager-timed-media-archiver-trigger-group";
73    public static final String TRIGGER_NAME = "oc-asset-manager-timed-media-archiver-trigger";
74  
75    private AssetManager assetManager;
76    private WorkflowService workflowService;
77    private String storeId;
78    private long ageModifier;
79  
80    public TimedMediaArchiver() {
81      try {
82        quartz = new StdSchedulerFactory().getScheduler();
83        quartz.start();
84        // create and set the job. To actually run it call schedule(..)
85        final JobDetail job = new JobDetail(getJobName(), getJobGroup(), Runner.class);
86        job.setDurability(false);
87        job.setVolatility(true);
88        job.getJobDataMap().put(JOB_PARAM_PARENT, this);
89        quartz.addJob(job, true);
90      } catch (org.quartz.SchedulerException e) {
91        throw new RuntimeException(e);
92      }
93    }
94  
95    @Activate
96    @Override
97    public void activate(ComponentContext cc) {
98      super.activate(cc);
99    }
100 
101   @Deactivate
102   @Override
103   public void deactivate() {
104     super.deactivate();
105   }
106 
107   public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
108     String cronExpression;
109     boolean enabled;
110 
111     unschedule();
112 
113     if (properties != null) {
114       logger.debug("Updating configuration...");
115 
116       enabled = BooleanUtils.toBoolean((String) properties.get(PARAM_KEY_ENABLED));
117       setEnabled(enabled);
118       logger.info("Timed media offload enabled: " + enabled);
119       if (!isEnabled()) {
120         return;
121       }
122 
123       cronExpression = (String) properties.get(PARAM_KEY_CRON_EXPR);
124       if (StringUtils.isBlank(cronExpression) || !CronExpression.isValidExpression(cronExpression)) {
125         throw new ConfigurationException(PARAM_KEY_CRON_EXPR, "Cron expression must be valid");
126       }
127       setCronExpression(cronExpression);
128       logger.debug("Timed media offload cron expression: '" + cronExpression + "'");
129 
130       storeId = (String) properties.get(PARAM_KEY_STORE_ID);
131       if (StringUtils.isBlank(storeId)) {
132         throw new ConfigurationException(PARAM_KEY_STORE_ID, "Store type is missing");
133       }
134       logger.debug("Remote media store type: " + storeId);
135 
136       try {
137         ageModifier = Long.parseLong((String) properties.get(PARAM_KEY_MAX_AGE));
138       } catch (NumberFormatException e) {
139         throw new ConfigurationException(PARAM_KEY_MAX_AGE, "Invalid max age");
140       }
141       if (ageModifier < 0) {
142         throw new ConfigurationException(PARAM_KEY_MAX_AGE, "Max age must be greater than zero");
143       }
144     }
145 
146     schedule();
147   }
148 
149   @Override
150   public String getJobGroup() {
151     return JOB_GROUP;
152   }
153 
154   @Override
155   public String getJobName() {
156     return JOB_NAME;
157   }
158 
159   @Override
160   public String getTriggerGroupName() {
161     return TRIGGER_GROUP;
162   }
163 
164   @Override
165   public String getTriggerName() {
166     return TRIGGER_NAME;
167   }
168 
169   @Override
170   public void scan() {
171     Date maxAge = Calendar.getInstance().getTime();
172     maxAge.setTime(maxAge.getTime() - TimeUnit.HOURS.toMillis(ageModifier));
173     if (assetManager.getAssetStore(storeId).isEmpty()) {
174       throw new RuntimeException("Store " + storeId + " is not available to the asset manager");
175     }
176 
177     try {
178       // Hardcoded date of zero.  Assumption: there is nothing with a date older than 0 which needs to be auto moved.
179       assetManager.moveSnapshotsByDate(new Date(0), maxAge, storeId);
180     } catch (NotFoundException ignore) {
181       logger.debug("No snapshots found that need to be moved");
182     }
183   }
184 
185   @Override
186   public String getScannerName() {
187     return SCANNER_NAME;
188   }
189 
190   @Reference
191   public void setAssetManager(AssetManager am) {
192     this.assetManager = am;
193   }
194 
195   /** Quartz job to which offloads old mediapackages from the asset manager to remote storage */
196   public static class Runner extends TypedQuartzJob<AbstractScanner> {
197     private static final NeedleEye eye = new NeedleEye();
198 
199     public Runner() {
200       super(some(eye));
201     }
202 
203     @Override
204     protected void execute(final AbstractScanner parameters, JobExecutionContext ctx) {
205       logger.debug("Starting " + parameters.getScannerName() + " job.");
206 
207       // iterate all organizations
208       for (final Organization org : parameters.getOrganizationDirectoryService().getOrganizations()) {
209         // set the organization on the current thread
210         parameters.getAdminContextFor(org.getId()).runInContext(parameters::scan);
211       }
212 
213       logger.debug("Finished " + parameters.getScannerName() + " job.");
214     }
215   }
216 
217   @Reference
218   @Override
219   public void bindOrganizationDirectoryService(OrganizationDirectoryService organizationDirectoryService) {
220     super.bindOrganizationDirectoryService(organizationDirectoryService);
221   }
222 
223   @Reference
224   @Override
225   public void bindSecurityService(SecurityService securityService) {
226     super.bindSecurityService(securityService);
227   }
228 
229   @Reference
230   @Override
231   public void bindServiceRegistry(ServiceRegistry serviceRegistry) {
232     super.bindServiceRegistry(serviceRegistry);
233   }
234 
235 }