Skip to main content
summaryrefslogtreecommitdiffstats
blob: 219ec9e84e8bc7bbb06bb5309452e2dbc37015a3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
/*
 * Copyright (c) OSGi Alliance (2004, 2010). All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.osgi.service.application;

import java.lang.reflect.Array;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.equinox.internal.app.AppPersistence;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;

/**
 * An OSGi service that represents an installed application and stores
 * information about it. The application descriptor can be used for instance
 * creation.
 * 
 * @version $Revision: 1.13 $
 */

public abstract class ApplicationDescriptor {
	/*
	 * NOTE: An implementor may also choose to replace this class in
	 * their distribution with a class that directly interfaces with the
	 * org.osgi.service.application implementation. This replacement class MUST NOT alter the
	 * public/protected signature of this class.
	 */

	/**
	 * The property key for the localized name of the application.
	 */
	public static final String APPLICATION_NAME = "application.name";

	/**
	 * The property key for the localized icon of the application.
	 */
	public static final String APPLICATION_ICON = "application.icon";

	/**
	 * The property key for the unique identifier (PID) of the application.
	 */
	public static final String APPLICATION_PID = Constants.SERVICE_PID;

	/**
	 * The property key for the version of the application.
	 */
	public static final String APPLICATION_VERSION = "application.version";

	/**
	 * The property key for the name of the application vendor.
	 */
	public static final String APPLICATION_VENDOR = Constants.SERVICE_VENDOR;

	/**
	 * The property key for the visibility property of the application.
	 */
	public static final String APPLICATION_VISIBLE = "application.visible";

	/**
	 * The property key for the launchable property of the application.
	 */
	public static final String APPLICATION_LAUNCHABLE = "application.launchable";

	/**
	 * The property key for the locked property of the application.
	 */
	public static final String APPLICATION_LOCKED = "application.locked";

	/**
	 * The property key for the localized description of the application.
	 */
	public static final String APPLICATION_DESCRIPTION = "application.description";

	/**
	 * The property key for the localized documentation of the application.
	 */
	public static final String APPLICATION_DOCUMENTATION = "application.documentation";

	/**
	 * The property key for the localized copyright notice of the application.
	 */
	public static final String APPLICATION_COPYRIGHT = "application.copyright";

	/**
	 * The property key for the localized license of the application.
	 */
	public static final String APPLICATION_LICENSE = "application.license";

	/**
	 * The property key for the application container of the application.
	 */
	public static final String APPLICATION_CONTAINER = "application.container";

	/**
	 * The property key for the location of the application.
	 */
	public static final String APPLICATION_LOCATION = "application.location";

	private final String pid;

	private final boolean[] locked = {false};

	/**
	 * Constructs the {@code ApplicationDescriptor}.
	 *
	 * @param applicationId
	 *            The identifier of the application. Its value is also available
	 *            as the {@code service.pid} service property of this 
	 *            {@code ApplicationDescriptor} service. This parameter must not
	 *            be {@code null}.
	 * @throws NullPointerException if the specified {@code applicationId} is null.
	 */
	protected ApplicationDescriptor(String applicationId) {
		if (null == applicationId) {
			throw new NullPointerException("Application ID must not be null!");
		}

		this.pid = applicationId;
		locked[0] = isPersistentlyLocked();
	}

	/**
	 * Returns the identifier of the represented application.
	 * 
	 * @return the identifier of the represented application
	 */
	public final String getApplicationId() {
		return pid;
	}

	/**
	 * This method verifies whether the specified {@code pattern}
	 * matches the Distinguished Names of any of the certificate chains
	 * used to authenticate this application.
	 * <P>
	 * The {@code pattern} must adhere to the 
	 * syntax defined in {@link org.osgi.service.application.ApplicationAdminPermission}
	 * for signer attributes. 
	 * <p>
	 * This method is used by {@link ApplicationAdminPermission#implies(java.security.Permission)} method
	 * to match target {@code ApplicationDescriptor} and filter. 
	 * 
	 * @param pattern a pattern for a chain of Distinguished Names. It must not be null.
	 * @return {@code true} if the specified pattern matches at least
	 *   one of the certificate chains used to authenticate this application 
	 * @throws NullPointerException if the specified {@code pattern} is null.
	 * @throws IllegalStateException if the application descriptor was
	 *   unregistered
	 */
	public abstract boolean matchDNChain(String pattern);

	/**
	 * Returns the properties of the application descriptor as key-value pairs.
	 * The return value contains the locale aware and unaware properties as
	 * well. The returned {@code Map} will include the service
	 * properties of this {@code ApplicationDescriptor} as well.
	 * <p>
	 * This method will call the {@code getPropertiesSpecific} method
	 * to enable the container implementation to insert application model and/or
	 * container implementation specific properties.
	 * <P>
	 * The returned {@link java.util.Map} will contain the standard OSGi service 
	 * properties as well
	 * (e.g. service.id, service.vendor etc.) and specialized application
	 * descriptors may offer further service properties. The returned Map contains
	 * a snapshot of the properties. It will not reflect further changes in the
	 * property values nor will the update of the Map change the corresponding
	 * service property.
	 *   
	 * @param locale
	 *            the locale string, it may be null, the value null means the
	 *            default locale. If the provided locale is the empty String 
	 *            ({@code ""})then raw (non-localized) values are returned.
	 * 
	 * @return copy of the service properties of this application descriptor service,
	 *         according to the specified locale. If locale is null then the
	 *         default locale's properties will be returned. (Since service
	 *         properties are always exist it cannot return null.)
	 * 
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	public final Map getProperties(String locale) {
		Map props = getPropertiesSpecific(locale);
		Boolean containerLocked = (Boolean) props.remove(APPLICATION_LOCKED);
		synchronized (locked) {
			if (containerLocked != null && containerLocked.booleanValue() != locked[0]) {
				if (locked[0])
					lockSpecific();
				else
					unlockSpecific();
			}
		}
		/* replace the container's lock with the application model's lock, that's the correct */
		props.put(APPLICATION_LOCKED, Boolean.valueOf(locked[0]));
		return props;
	}

	/**
	 * Container implementations can provide application model specific
	 * and/or container implementation specific properties via this 
	 * method. 
	 * 
	 * Localizable properties must be returned localized if the provided
	 * {@code locale} argument is not the empty String. The value
	 * {@code null} indicates to use the default locale, for other
	 * values the specified locale should be used.
	 *  
	 * The returned {@link java.util.Map} must contain the standard OSGi service 
	 * properties as well
	 * (e.g. service.id, service.vendor etc.) and specialized application
	 * descriptors may offer further service properties. 
	 * The returned {@code Map}
	 * contains a snapshot of the properties. It will not reflect further changes in the
	 * property values nor will the update of the Map change the corresponding
	 * service property.

	 * @param locale the locale to be used for localizing the properties.
	 * If {@code null} the default locale should be used. If it is
	 * the empty String ({@code ""}) then raw (non-localized) values
	 * should be returned.
	 * 
	 * @return the application model specific and/or container implementation
	 * specific properties of this application descriptor.
	 *  
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	protected abstract Map getPropertiesSpecific(String locale);

	/**
	 * Launches a new instance of an application. The {@code args} parameter
	 * specifies the startup parameters for the instance to be launched, it may
	 * be null.
	 * <p>
	 * The following steps are made:
	 * <UL>
	 * <LI>Check for the appropriate permission.
	 * <LI>Check the locking state of the application. If locked then throw an
	 * {@link ApplicationException} with the reason code
	 * {@link ApplicationException#APPLICATION_LOCKED}.
	 * <LI>Calls the {@code launchSpecific()} method to create and start an
	 * application instance.
	 * <LI>Returns the {@code ApplicationHandle} returned by the
	 * launchSpecific()
	 * </UL>
	 * The caller has to have ApplicationAdminPermission(applicationPID,
	 * "launch") in order to be able to perform this operation.
	 * <P>
	 * The {@code Map} argument of the launch method contains startup arguments
	 * for the application. The keys used in the Map must be non-null, non-empty
	 * {@code String} objects. They can be standard or application specific.
	 * OSGi defines the {@code org.osgi.triggeringevent} key to be used to pass
	 * the triggering event to a scheduled application, however in the future it
	 * is possible that other well-known keys will be defined. To avoid unwanted
	 * clashes of keys, the following rules should be applied:
	 * <ul>
	 * <li>The keys starting with the dash (-) character are application
	 * specific, no well-known meaning should be associated with them.</li>
	 * <li>Well-known keys should follow the reverse domain name based naming.
	 * In particular, the keys standardized in OSGi should start with
	 * {@code org.osgi.}.</li>
	 * </ul>
	 * <P>
	 * The method is synchronous, it return only when the application instance
	 * was successfully started or the attempt to start it failed.
	 * <P>
	 * This method never returns {@code null}. If launching an application
	 * fails, the appropriate exception is thrown.
	 * 
	 * @param arguments Arguments for the newly launched application, may be
	 *        null
	 * 
	 * @return the registered ApplicationHandle, which represents the newly
	 *         launched application instance. Never returns {@code null}.
	 * 
	 * @throws SecurityException if the caller doesn't have "lifecycle"
	 *         ApplicationAdminPermission for the application.
	 * @throws ApplicationException if starting the application failed
	 * @throws IllegalStateException if the application descriptor is
	 *         unregistered
	 * @throws IllegalArgumentException if the specified {@code Map} contains
	 *         invalid keys (null objects, empty {@code String} or a key that is
	 *         not {@code String})
	 */
	public final ApplicationHandle launch(Map arguments) throws ApplicationException {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null)
			sm.checkPermission(new ApplicationAdminPermission(this, ApplicationAdminPermission.LIFECYCLE_ACTION));
		synchronized (locked) {
			if (locked[0])
				throw new ApplicationException(ApplicationException.APPLICATION_LOCKED, "Application is locked, can't launch!");
		}
		if (!isLaunchableSpecific())
			throw new ApplicationException(ApplicationException.APPLICATION_NOT_LAUNCHABLE, "Cannot launch the application!");
		checkArgs(arguments, false);
		try {
			return launchSpecific(arguments);
		} catch (IllegalStateException | SecurityException | ApplicationException ise) {
			throw ise;
		} catch (Exception t) {
			throw new ApplicationException(ApplicationException.APPLICATION_INTERNAL_ERROR, t);
		}
	}

	/**
	 * Called by launch() to create and start a new instance in an application
	 * model specific way. It also creates and registeres the application handle
	 * to represent the newly created and started instance and registeres it.
	 * The method is synchronous, it return only when the application instance was
	 * successfully started or the attempt to start it failed.
	 * <P>
	 * This method must not return {@code null}. If launching the application
	 * failed, and exception must be thrown.
	 * 
	 * @param arguments
	 *            the startup parameters of the new application instance, may be
	 *            null
	 * 
	 * @return the registered application model
	 *         specific application handle for the newly created and started
	 *         instance.
	 * 
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 * @throws Exception
	 *             if any problem occurs.
	 */
	protected abstract ApplicationHandle launchSpecific(Map arguments) throws Exception;

	/**
	 * This method is called by launch() to verify that according to the
	 * container, the application is launchable.
	 * 
	 * @return true, if the application is launchable according to the 
	 *  container, false otherwise.
	 *  
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	protected abstract boolean isLaunchableSpecific();

	/**
	 * Schedules the application at a specified event. Schedule information
	 * should not get lost even if the framework or the device restarts so it
	 * should be stored in a persistent storage. The method registers a
	 * {@link ScheduledApplication} service in Service Registry, representing
	 * the created schedule.
	 * <p>
	 * The {@code Map} argument of the method contains startup arguments for the
	 * application. The keys used in the Map must be non-null, non-empty
	 * {@code String} objects. The argument values must be of primitive types,
	 * wrapper classes of primitive types, {@code String} or arrays or
	 * collections of these.
	 * <p>
	 * The created schedules have a unique identifier within the scope of this
	 * {@code ApplicationDescriptor}. This identifier can be specified in the
	 * {@code scheduleId} argument. If this argument is {@code null}, the
	 * identifier is automatically generated.
	 * 
	 * @param scheduleId the identifier of the created schedule. It can be
	 *        {@code null}, in this case the identifier is automatically
	 *        generated.
	 * @param arguments the startup arguments for the scheduled application, may
	 *        be null
	 * @param topic specifies the topic of the triggering event, it may contain
	 *        a trailing asterisk as wildcard, the empty string is treated as
	 *        "*", must not be null
	 * @param eventFilter specifies and LDAP filter to filter on the properties
	 *        of the triggering event, may be null
	 * @param recurring if the recurring parameter is false then the application
	 *        will be launched only once, when the event firstly occurs. If the
	 *        parameter is true then scheduling will take place for every event
	 *        occurrence; i.e. it is a recurring schedule
	 * 
	 * @return the registered scheduled application service
	 * 
	 * @throws NullPointerException if the topic is {@code null}
	 * @throws InvalidSyntaxException if the specified {@code eventFilter} is
	 *         not syntactically correct
	 * @throws ApplicationException if the schedule couldn't be created. The
	 *         possible error codes are
	 *         <ul>
	 *         <li>
	 *         {@link ApplicationException#APPLICATION_DUPLICATE_SCHEDULE_ID} if
	 *         the specified {@code scheduleId} is already used for this
	 *         {@code ApplicationDescriptor} <li>
	 *         {@link ApplicationException#APPLICATION_SCHEDULING_FAILED} if the
	 *         scheduling failed due to some internal reason (e.g. persistent
	 *         storage error). <li>
	 *         {@link ApplicationException#APPLICATION_INVALID_STARTUP_ARGUMENT}
	 *         if the specified startup argument doesn't satisfy the type or
	 *         value constraints of startup arguments.
	 *         </ul>
	 * @throws SecurityException if the caller doesn't have "schedule"
	 *         ApplicationAdminPermission for the application.
	 * @throws IllegalStateException if the application descriptor is
	 *         unregistered
	 * @throws IllegalArgumentException if the specified {@code Map} contains
	 *         invalid keys (null objects, empty {@code String} or a key that is
	 *         not {@code String})
	 */
	public final ScheduledApplication schedule(String scheduleId, Map arguments, String topic, String eventFilter, boolean recurring) throws InvalidSyntaxException, ApplicationException {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null)
			sm.checkPermission(new ApplicationAdminPermission(this, ApplicationAdminPermission.SCHEDULE_ACTION));
		arguments = checkArgs(arguments, true);
		isLaunchableSpecific(); // checks if the ApplicationDescriptor was already unregistered
		return AppPersistence.addScheduledApp(this, scheduleId, arguments, topic, eventFilter, recurring);
	}

	/**
	 * Sets the lock state of the application. If an application is locked then
	 * launching a new instance is not possible. It does not affect the already
	 * launched instances.
	 * 
	 * @throws SecurityException
	 *             if the caller doesn't have "lock" ApplicationAdminPermission
	 *             for the application.
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	public final void lock() {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null)
			sm.checkPermission(new ApplicationAdminPermission(this, ApplicationAdminPermission.LOCK_ACTION));
		synchronized (locked) {
			if (locked[0])
				return;
			locked[0] = true;
			lockSpecific();
			saveLock(true);
		}
	}

	/**
	 * This method is used to notify the container implementation that the
	 * corresponding application has been locked and it should update the
	 * {@code application.locked} service property accordingly.
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	protected abstract void lockSpecific();

	/**
	 * Unsets the lock state of the application.
	 * 
	 * @throws SecurityException
	 *             if the caller doesn't have "lock" ApplicationAdminPermission
	 *             for the application.
	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	public final void unlock() {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null)
			sm.checkPermission(new ApplicationAdminPermission(this, ApplicationAdminPermission.LOCK_ACTION));
		synchronized (locked) {
			if (!locked[0])
				return;
			locked[0] = false;
			unlockSpecific();
			saveLock(false);
		}
	}

	/**
	 * This method is used to notify the container implementation that the
	 * corresponding application has been unlocked and it should update the
	 * {@code application.locked} service property accordingly.

	 * @throws IllegalStateException
	 *             if the application descriptor is unregistered
	 */
	protected abstract void unlockSpecific();

	private void saveLock(boolean locked) {
		AppPersistence.saveLock(this, locked);
	}

	private boolean isPersistentlyLocked() {
		return AppPersistence.isLocked(this);
	}

	private static final Collection scalars = Arrays.asList(new Class[] {String.class, Integer.class, Long.class, Float.class, Double.class, Byte.class, Short.class, Character.class, Boolean.class});
	private static final Collection scalarsArrays = Arrays.asList(new Class[] {String[].class, Integer[].class, Long[].class, Float[].class, Double[].class, Byte[].class, Short[].class, Character[].class, Boolean[].class});
	private static final Collection primitiveArrays = Arrays.asList(new Class[] {long[].class, int[].class, short[].class, char[].class, byte[].class, double[].class, float[].class, boolean[].class});

	private static Map checkArgs(Map arguments, boolean validateValues) throws ApplicationException {
		if (arguments == null)
			return arguments;
		Map copy = validateValues ? new HashMap() : null;
		for (Iterator entries = arguments.entrySet().iterator(); entries.hasNext();) {
			Map.Entry entry = (Entry) entries.next();
			if (!(entry.getKey() instanceof String))
				throw new IllegalArgumentException("Invalid key type: " + entry.getKey() == null ? "<null>" : entry.getKey().getClass().getName());
			if ("".equals(entry.getKey())) //$NON-NLS-1$
				throw new IllegalArgumentException("Empty string is an invalid key");
			if (validateValues)
				validateValue(entry, copy);
		}
		return validateValues ? copy : arguments;
	}

	private static void validateValue(Map.Entry entry, Map copy) throws ApplicationException {
		Class clazz = entry.getValue().getClass();

		// Is it in the set of scalar types	
		if (scalars.contains(clazz)) {
			copy.put(entry.getKey(), entry.getValue());
			return;
		}

		// Is it an array of primitives or scalars
		if (scalarsArrays.contains(clazz) || primitiveArrays.contains(clazz)) {
			int arrayLength = Array.getLength(entry.getValue());
			Object copyOfArray = Array.newInstance(entry.getValue().getClass().getComponentType(), arrayLength);
			System.arraycopy(entry.getValue(), 0, copyOfArray, 0, arrayLength);
			copy.put(entry.getKey(), copyOfArray);
			return;
		}

		// Is it a Collection of scalars
		if (entry.getValue() instanceof Collection) {
			Collection valueCollection = (Collection) entry.getValue();
			for (Iterator it = valueCollection.iterator(); it.hasNext();) {
				Class containedClazz = it.next().getClass();
				if (!scalars.contains(containedClazz)) {
					throw new ApplicationException(ApplicationException.APPLICATION_INVALID_STARTUP_ARGUMENT, "The value for key \"" + entry.getKey() + "\" is a collection that contains an invalid value of type \"" + containedClazz.getName() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
			}
			copy.put(entry.getKey(), new ArrayList((Collection) entry.getValue()));
			return;
		}
		throw new ApplicationException(ApplicationException.APPLICATION_INVALID_STARTUP_ARGUMENT, "The value for key \"" + entry.getKey() + "\" is an invalid type \"" + clazz.getName() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}
}

Back to the top