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.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
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
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
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
208 for (final Organization org : parameters.getOrganizationDirectoryService().getOrganizations()) {
209
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 }