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.composer.impl;
23
24 import static org.opencastproject.util.ReadinessIndicator.ARTIFACT;
25
26 import org.opencastproject.composer.api.EncodingProfile;
27 import org.opencastproject.composer.api.EncodingProfile.MediaType;
28 import org.opencastproject.composer.api.EncodingProfileImpl;
29 import org.opencastproject.util.ConfigurationException;
30 import org.opencastproject.util.ReadinessIndicator;
31
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.felix.fileinstall.ArtifactInstaller;
34 import org.osgi.framework.BundleContext;
35 import org.osgi.service.component.annotations.Activate;
36 import org.osgi.service.component.annotations.Component;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FilenameFilter;
43 import java.io.IOException;
44 import java.io.InputStreamReader;
45 import java.nio.charset.StandardCharsets;
46 import java.util.ArrayList;
47 import java.util.Dictionary;
48 import java.util.HashMap;
49 import java.util.Hashtable;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Properties;
54 import java.util.Set;
55
56
57
58
59 @Component(
60 property = {
61 "service.description=Encoding Profile Scanner"
62 },
63 immediate = true,
64 service = { EncodingProfileScanner.class, ArtifactInstaller.class }
65 )
66 public class EncodingProfileScanner implements ArtifactInstaller {
67
68
69 private static final String PROP_PREFIX = "profile.";
70
71
72 private static final String PROP_NAME = ".name";
73 private static final String PROP_APPLICABLE = ".input";
74 private static final String PROP_OUTPUT = ".output";
75 private static final String PROP_SUFFIX = ".suffix";
76 private static final String PROP_JOBLOAD = ".jobload";
77
78
79 private BundleContext bundleCtx = null;
80
81
82 private int sumInstalledFiles = 0;
83
84
85 private int sumUnparsableFiles = 0;
86
87
88 private Map<String, EncodingProfile> profiles = new HashMap<String, EncodingProfile>();
89
90
91 private static final Logger logger = LoggerFactory.getLogger(EncodingProfileScanner.class);
92
93
94
95
96
97
98 public Map<String, EncodingProfile> getProfiles() {
99 return profiles;
100 }
101
102
103
104
105
106
107
108 @Activate
109 void activate(BundleContext ctx) {
110 this.bundleCtx = ctx;
111 }
112
113
114
115
116
117
118
119
120 public EncodingProfile getProfile(String id) {
121 return profiles.get(id);
122 }
123
124
125
126
127
128
129
130
131 Map<String, EncodingProfile> loadFromProperties(File artifact) throws IOException {
132
133 Properties properties = new Properties();
134 try (InputStreamReader reader = new InputStreamReader(new FileInputStream(artifact), StandardCharsets.UTF_8)) {
135 properties.load(reader);
136 }
137
138
139 List<String> profileNames = new ArrayList<>();
140 for (Object fullKey : properties.keySet()) {
141 String key = fullKey.toString();
142 if (key.startsWith(PROP_PREFIX) && key.endsWith(PROP_NAME)) {
143 int separatorLocation = fullKey.toString().lastIndexOf('.');
144 key = key.substring(PROP_PREFIX.length(), separatorLocation);
145 if (!profileNames.contains(key)) {
146 profileNames.add(key);
147 } else {
148 throw new ConfigurationException("Found duplicate definition for encoding profile '" + key + "'");
149 }
150 }
151 }
152
153
154 Map<String, EncodingProfile> profiles = new HashMap<>();
155 for (String profileId : profileNames) {
156 logger.debug("Enabling media format " + profileId);
157 EncodingProfile profile = loadProfile(profileId, properties, artifact);
158 profiles.put(profileId, profile);
159 }
160
161 return profiles;
162 }
163
164
165
166
167
168
169
170
171
172
173 private EncodingProfile loadProfile(String profile, Properties properties, File artifact)
174 throws ConfigurationException {
175 List<String> defaultProperties = new ArrayList<>(10);
176
177 String name = getDefaultProperty(profile, PROP_NAME, properties, defaultProperties);
178 if (StringUtils.isBlank(name)) {
179 throw new ConfigurationException("Distribution profile '" + profile + "' is missing a name (" + PROP_NAME + ").");
180 }
181
182 EncodingProfileImpl df = new EncodingProfileImpl(profile, name, artifact);
183
184
185 String type = getDefaultProperty(profile, PROP_OUTPUT, properties, defaultProperties);
186 if (StringUtils.isBlank(type))
187 throw new ConfigurationException("Output type (" + PROP_OUTPUT + ") of profile '" + profile + "' is missing");
188 try {
189 df.setOutputType(MediaType.parseString(StringUtils.trimToEmpty(type)));
190 } catch (IllegalArgumentException e) {
191 throw new ConfigurationException("Output type (" + PROP_OUTPUT + ") '" + type + "' of profile '" + profile
192 + "' is unknown");
193 }
194
195
196 List<String> tags = getTags(profile, properties, defaultProperties);
197 if (tags.size() > 0) {
198 for (String tag : tags) {
199 String prop = PROP_SUFFIX + "." + tag;
200 String suffixObj = getDefaultProperty(profile, prop, properties, defaultProperties);
201 df.setSuffix(tag, StringUtils.trim(suffixObj));
202 }
203 } else {
204
205 String suffixObj = getDefaultProperty(profile, PROP_SUFFIX, properties, defaultProperties);
206 if (StringUtils.isBlank(suffixObj))
207 throw new ConfigurationException("Suffix (" + PROP_SUFFIX + ") of profile '" + profile + "' is missing");
208 df.setSuffix(StringUtils.trim(suffixObj));
209 }
210
211
212 String applicableObj = getDefaultProperty(profile, PROP_APPLICABLE, properties, defaultProperties);
213 if (StringUtils.isBlank(applicableObj))
214 throw new ConfigurationException("Input type (" + PROP_APPLICABLE + ") of profile '" + profile + "' is missing");
215 df.setApplicableType(MediaType.parseString(StringUtils.trimToEmpty(applicableObj)));
216
217 String jobLoad = getDefaultProperty(profile, PROP_JOBLOAD, properties, defaultProperties);
218 if (!StringUtils.isBlank(jobLoad)) {
219 df.setJobLoad(Float.valueOf(jobLoad));
220 logger.debug("Setting job load for profile {} to {}", profile, jobLoad);
221 }
222
223
224 String extensionKey = PROP_PREFIX + profile + ".";
225 for (Map.Entry<Object, Object> entry : properties.entrySet()) {
226 String key = entry.getKey().toString();
227 if (key.startsWith(extensionKey) && !defaultProperties.contains(key)) {
228 String k = key.substring(extensionKey.length());
229 String v = StringUtils.trimToEmpty(entry.getValue().toString());
230 df.addExtension(k, v);
231 }
232 }
233
234 return df;
235 }
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 private static String getDefaultProperty(String profile, String keySuffix, Properties properties, List<String> list) {
251 String key = PROP_PREFIX + profile + keySuffix;
252 list.add(key);
253 return StringUtils.trimToNull(properties.getProperty(key));
254 }
255
256
257
258
259
260
261
262
263
264
265
266
267 private static List<String> getTags(String profile, Properties properties, List<String> list) {
268 Set<Object> keys = properties.keySet();
269 String key = PROP_PREFIX + profile + PROP_SUFFIX;
270
271 ArrayList<String> tags = new ArrayList<>();
272 for (Object o : keys) {
273 String k = o.toString();
274 if (k.startsWith(key)) {
275 if (k.substring(key.length()).length() > 0) {
276 list.add(k);
277 tags.add(k.substring(key.length() + 1));
278 }
279 }
280 }
281 return tags;
282 }
283
284
285
286
287
288
289 @Override
290 public boolean canHandle(File artifact) {
291 return "encoding".equals(artifact.getParentFile().getName()) && artifact.getName().endsWith(".properties");
292 }
293
294
295
296
297
298
299 @Override
300 public void install(File artifact) throws Exception {
301 logger.info("Registering encoding profiles from {}", artifact);
302 try {
303 Map<String, EncodingProfile> profileMap = loadFromProperties(artifact);
304 for (Map.Entry<String, EncodingProfile> entry : profileMap.entrySet()) {
305 EncodingProfile profile = entry.getValue();
306 logger.info("Installed profile {} (load {})", profile.getIdentifier(), profile.getJobLoad());
307 profiles.put(entry.getKey(), profile);
308 }
309 sumInstalledFiles++;
310 } catch (Exception e) {
311 logger.error("Encoding profiles could not be read from {}: {}", artifact, e.getMessage());
312 sumUnparsableFiles++;
313 }
314
315
316 String[] filesInDirectory = artifact.getParentFile().list(new FilenameFilter() {
317 public boolean accept(File arg0, String name) {
318 return name.endsWith(".properties");
319 }
320 });
321
322
323 if (filesInDirectory.length == (sumInstalledFiles + sumUnparsableFiles)) {
324 Dictionary<String, String> properties = new Hashtable<String, String>();
325 properties.put(ARTIFACT, "encodingprofile");
326 logger.debug("Indicating readiness of encoding profiles");
327 bundleCtx.registerService(ReadinessIndicator.class.getName(), new ReadinessIndicator(), properties);
328
329 if (filesInDirectory.length == sumInstalledFiles) {
330 logger.info("All {} encoding profiles installed", filesInDirectory.length);
331 } else {
332 logger.warn("{} encoding profile(s) installed, {} encoding profile(s) could not be installed",
333 sumInstalledFiles, sumUnparsableFiles);
334 }
335 } else {
336 logger.debug("{} of {} encoding profiles installed", sumInstalledFiles, filesInDirectory.length);
337 }
338 }
339
340
341
342
343
344
345 @Override
346 public void uninstall(File artifact) throws Exception {
347 for (Iterator<EncodingProfile> iter = profiles.values().iterator(); iter.hasNext();) {
348 EncodingProfile profile = iter.next();
349 if (artifact.equals(profile.getSource())) {
350 logger.info("Uninstalling profile {}", profile.getIdentifier());
351 iter.remove();
352 }
353 }
354 }
355
356
357
358
359
360
361 @Override
362 public void update(File artifact) throws Exception {
363 uninstall(artifact);
364 install(artifact);
365 }
366
367 }