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 }
189 try {
190 df.setOutputType(MediaType.parseString(StringUtils.trimToEmpty(type)));
191 } catch (IllegalArgumentException e) {
192 throw new ConfigurationException("Output type (" + PROP_OUTPUT + ") '" + type + "' of profile '" + profile
193 + "' is unknown");
194 }
195
196
197 List<String> tags = getTags(profile, properties, defaultProperties);
198 if (tags.size() > 0) {
199 for (String tag : tags) {
200 String prop = PROP_SUFFIX + "." + tag;
201 String suffixObj = getDefaultProperty(profile, prop, properties, defaultProperties);
202 df.setSuffix(tag, StringUtils.trim(suffixObj));
203 }
204 } else {
205
206 String suffixObj = getDefaultProperty(profile, PROP_SUFFIX, properties, defaultProperties);
207 if (StringUtils.isBlank(suffixObj)) {
208 throw new ConfigurationException("Suffix (" + PROP_SUFFIX + ") of profile '" + profile + "' is missing");
209 }
210 df.setSuffix(StringUtils.trim(suffixObj));
211 }
212
213
214 String applicableObj = getDefaultProperty(profile, PROP_APPLICABLE, properties, defaultProperties);
215 if (StringUtils.isBlank(applicableObj)) {
216 throw new ConfigurationException("Input type (" + PROP_APPLICABLE + ") of profile '" + profile + "' is missing");
217 }
218 df.setApplicableType(MediaType.parseString(StringUtils.trimToEmpty(applicableObj)));
219
220 String jobLoad = getDefaultProperty(profile, PROP_JOBLOAD, properties, defaultProperties);
221 if (!StringUtils.isBlank(jobLoad)) {
222 df.setJobLoad(Float.valueOf(jobLoad));
223 logger.debug("Setting job load for profile {} to {}", profile, jobLoad);
224 }
225
226
227 String extensionKey = PROP_PREFIX + profile + ".";
228 for (Map.Entry<Object, Object> entry : properties.entrySet()) {
229 String key = entry.getKey().toString();
230 if (key.startsWith(extensionKey) && !defaultProperties.contains(key)) {
231 String k = key.substring(extensionKey.length());
232 String v = StringUtils.trimToEmpty(entry.getValue().toString());
233 df.addExtension(k, v);
234 }
235 }
236
237 return df;
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 private static String getDefaultProperty(String profile, String keySuffix, Properties properties, List<String> list) {
254 String key = PROP_PREFIX + profile + keySuffix;
255 list.add(key);
256 return StringUtils.trimToNull(properties.getProperty(key));
257 }
258
259
260
261
262
263
264
265
266
267
268
269
270 private static List<String> getTags(String profile, Properties properties, List<String> list) {
271 Set<Object> keys = properties.keySet();
272 String key = PROP_PREFIX + profile + PROP_SUFFIX;
273
274 ArrayList<String> tags = new ArrayList<>();
275 for (Object o : keys) {
276 String k = o.toString();
277 if (k.startsWith(key)) {
278 if (k.substring(key.length()).length() > 0) {
279 list.add(k);
280 tags.add(k.substring(key.length() + 1));
281 }
282 }
283 }
284 return tags;
285 }
286
287
288
289
290
291
292 @Override
293 public boolean canHandle(File artifact) {
294 return "encoding".equals(artifact.getParentFile().getName()) && artifact.getName().endsWith(".properties");
295 }
296
297
298
299
300
301
302 @Override
303 public void install(File artifact) throws Exception {
304 logger.info("Registering encoding profiles from {}", artifact);
305 try {
306 Map<String, EncodingProfile> profileMap = loadFromProperties(artifact);
307 for (Map.Entry<String, EncodingProfile> entry : profileMap.entrySet()) {
308 EncodingProfile profile = entry.getValue();
309 logger.info("Installed profile {} (load {})", profile.getIdentifier(), profile.getJobLoad());
310 profiles.put(entry.getKey(), profile);
311 }
312 sumInstalledFiles++;
313 } catch (Exception e) {
314 logger.error("Encoding profiles could not be read from {}: {}", artifact, e.getMessage());
315 sumUnparsableFiles++;
316 }
317
318
319 String[] filesInDirectory = artifact.getParentFile().list(new FilenameFilter() {
320 public boolean accept(File arg0, String name) {
321 return name.endsWith(".properties");
322 }
323 });
324
325
326 if (filesInDirectory.length == (sumInstalledFiles + sumUnparsableFiles)) {
327 Dictionary<String, String> properties = new Hashtable<String, String>();
328 properties.put(ARTIFACT, "encodingprofile");
329 logger.debug("Indicating readiness of encoding profiles");
330 bundleCtx.registerService(ReadinessIndicator.class.getName(), new ReadinessIndicator(), properties);
331
332 if (filesInDirectory.length == sumInstalledFiles) {
333 logger.info("All {} encoding profiles in {} files installed", profiles.size(), filesInDirectory.length);
334 } else {
335 logger.warn("{} encoding config files installed, {} encoding config files could not be installed",
336 sumInstalledFiles, sumUnparsableFiles);
337 }
338 } else {
339 logger.debug("{} of {} encoding profile config files installed", sumInstalledFiles, filesInDirectory.length);
340 }
341 }
342
343
344
345
346
347
348 @Override
349 public void uninstall(File artifact) throws Exception {
350 for (Iterator<EncodingProfile> iter = profiles.values().iterator(); iter.hasNext();) {
351 EncodingProfile profile = iter.next();
352 if (artifact.equals(profile.getSource())) {
353 logger.info("Uninstalling profile {}", profile.getIdentifier());
354 iter.remove();
355 }
356 }
357 }
358
359
360
361
362
363
364 @Override
365 public void update(File artifact) throws Exception {
366 uninstall(artifact);
367 install(artifact);
368 }
369
370 }