001 /*
002 * Copyright (c) OSGi Alliance (2005, 2009). All Rights Reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.osgi.service.event;
018
019 import java.io.IOException;
020 import java.security.Permission;
021 import java.security.PermissionCollection;
022 import java.util.Enumeration;
023 import java.util.Hashtable;
024
025 /**
026 * A bundle's authority to publish or subscribe to event on a topic.
027 *
028 * <p>
029 * A topic is a slash-separated string that defines a topic.
030 * <p>
031 * For example:
032 *
033 * <pre>
034 * org / osgi / service / foo / FooEvent / ACTION
035 * </pre>
036 *
037 * <p>
038 * <code>TopicPermission</code> has two actions: <code>publish</code> and
039 * <code>subscribe</code>.
040 *
041 * @ThreadSafe
042 * @version $Revision: 6381 $
043 */
044 public final class TopicPermission extends Permission {
045 static final long serialVersionUID = -5855563886961618300L;
046 /**
047 * The action string <code>publish</code>.
048 */
049 public final static String PUBLISH = "publish";
050 /**
051 * The action string <code>subscribe</code>.
052 */
053 public final static String SUBSCRIBE = "subscribe";
054 private final static int ACTION_PUBLISH = 0x00000001;
055 private final static int ACTION_SUBSCRIBE = 0x00000002;
056 private final static int ACTION_ALL = ACTION_PUBLISH
057 | ACTION_SUBSCRIBE;
058 private final static int ACTION_NONE = 0;
059 /**
060 * The actions mask.
061 */
062 private transient int action_mask;
063
064 /**
065 * prefix if the name is wildcarded.
066 */
067 private transient volatile String prefix;
068
069 /**
070 * The actions in canonical form.
071 *
072 * @serial
073 */
074 private volatile String actions = null;
075
076 /**
077 * Defines the authority to publich and/or subscribe to a topic within the
078 * EventAdmin service.
079 * <p>
080 * The name is specified as a slash-separated string. Wildcards may be used.
081 * For example:
082 *
083 * <pre>
084 * org/osgi/service/fooFooEvent/ACTION
085 * com/isv/*
086 * *
087 * </pre>
088 *
089 * <p>
090 * A bundle that needs to publish events on a topic must have the
091 * appropriate <code>TopicPermission</code> for that topic; similarly, a
092 * bundle that needs to subscribe to events on a topic must have the
093 * appropriate <code>TopicPermssion</code> for that topic.
094 * <p>
095 *
096 * @param name Topic name.
097 * @param actions <code>publish</code>,<code>subscribe</code> (canonical
098 * order).
099 */
100 public TopicPermission(String name, String actions) {
101 this(name, parseActions(actions));
102 }
103
104 /**
105 * Package private constructor used by TopicPermissionCollection.
106 *
107 * @param name class name
108 * @param mask action mask
109 */
110 TopicPermission(String name, int mask) {
111 super(name);
112 setTransients(mask);
113 }
114
115 /**
116 * Called by constructors and when deserialized.
117 *
118 * @param name topic name
119 * @param mask action mask
120 */
121 private synchronized void setTransients(final int mask) {
122 final String name = getName();
123 if ((name == null) || name.length() == 0) {
124 throw new IllegalArgumentException("invalid name");
125 }
126
127 if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
128 throw new IllegalArgumentException("invalid action string");
129 }
130 action_mask = mask;
131
132 if (name.equals("*")) {
133 prefix = "";
134 }
135 else {
136 if (name.endsWith("/*")) {
137 prefix = name.substring(0, name.length() - 1);
138 }
139 else {
140 prefix = null;
141 }
142 }
143 }
144
145 /**
146 * Returns the current action mask.
147 * <p>
148 * Used by the TopicPermissionCollection class.
149 *
150 * @return Current action mask.
151 */
152 synchronized int getActionsMask() {
153 return action_mask;
154 }
155
156 /**
157 * Parse action string into action mask.
158 *
159 * @param actions Action string.
160 * @return action mask.
161 */
162 private static int parseActions(final String actions) {
163 boolean seencomma = false;
164 int mask = ACTION_NONE;
165 if (actions == null) {
166 return mask;
167 }
168 char[] a = actions.toCharArray();
169 int i = a.length - 1;
170 if (i < 0)
171 return mask;
172 while (i != -1) {
173 char c;
174 // skip whitespace
175 while ((i != -1)
176 && ((c = a[i]) == ' ' || c == '\r' || c == '\n'
177 || c == '\f' || c == '\t'))
178 i--;
179 // check for the known strings
180 int matchlen;
181 if (i >= 8 && (a[i - 8] == 's' || a[i - 8] == 'S')
182 && (a[i - 7] == 'u' || a[i - 7] == 'U')
183 && (a[i - 6] == 'b' || a[i - 6] == 'B')
184 && (a[i - 5] == 's' || a[i - 5] == 'S')
185 && (a[i - 4] == 'c' || a[i - 4] == 'C')
186 && (a[i - 3] == 'r' || a[i - 3] == 'R')
187 && (a[i - 2] == 'i' || a[i - 2] == 'I')
188 && (a[i - 1] == 'b' || a[i - 1] == 'B')
189 && (a[i] == 'e' || a[i] == 'E')) {
190 matchlen = 9;
191 mask |= ACTION_SUBSCRIBE;
192 }
193 else
194 if (i >= 6 && (a[i - 6] == 'p' || a[i - 6] == 'P')
195 && (a[i - 5] == 'u' || a[i - 5] == 'U')
196 && (a[i - 4] == 'b' || a[i - 4] == 'B')
197 && (a[i - 3] == 'l' || a[i - 3] == 'L')
198 && (a[i - 2] == 'i' || a[i - 2] == 'I')
199 && (a[i - 1] == 's' || a[i - 1] == 'S')
200 && (a[i] == 'h' || a[i] == 'H')) {
201 matchlen = 7;
202 mask |= ACTION_PUBLISH;
203 }
204 else {
205 // parse error
206 throw new IllegalArgumentException("invalid permission: "
207 + actions);
208 }
209 // make sure we didn't just match the tail of a word
210 // like "ackbarfpublish". Also, skip to the comma.
211 seencomma = false;
212 while (i >= matchlen && !seencomma) {
213 switch (a[i - matchlen]) {
214 case ',' :
215 seencomma = true;
216 /* FALLTHROUGH */
217 case ' ' :
218 case '\r' :
219 case '\n' :
220 case '\f' :
221 case '\t' :
222 break;
223 default :
224 throw new IllegalArgumentException(
225 "invalid permission: " + actions);
226 }
227 i--;
228 }
229 // point i at the location of the comma minus one (or -1).
230 i -= matchlen;
231 }
232 if (seencomma) {
233 throw new IllegalArgumentException("invalid permission: " + actions);
234 }
235 return mask;
236 }
237
238 /**
239 * Determines if the specified permission is implied by this object.
240 *
241 * <p>
242 * This method checks that the topic name of the target is implied by the
243 * topic name of this object. The list of <code>TopicPermission</code>
244 * actions must either match or allow for the list of the target object to
245 * imply the target <code>TopicPermission</code> action.
246 *
247 * <pre>
248 * x/y/*,"publish" -> x/y/z,"publish" is true
249 * *,"subscribe" -> x/y,"subscribe" is true
250 * *,"publish" -> x/y,"subscribe" is false
251 * x/y,"publish" -> x/y/z,"publish" is false
252 * </pre>
253 *
254 * @param p The target permission to interrogate.
255 * @return <code>true</code> if the specified <code>TopicPermission</code>
256 * action is implied by this object; <code>false</code> otherwise.
257 */
258 public boolean implies(Permission p) {
259 if (p instanceof TopicPermission) {
260 TopicPermission requested = (TopicPermission) p;
261 int requestedMask = requested.getActionsMask();
262 if ((getActionsMask() & requestedMask) == requestedMask) {
263 String requestedName = requested.getName();
264 String pre = prefix;
265 if (pre != null) {
266 return requestedName.startsWith(pre);
267 }
268
269 return requestedName.equals(getName());
270 }
271 }
272 return false;
273 }
274
275 /**
276 * Returns the canonical string representation of the
277 * <code>TopicPermission</code> actions.
278 *
279 * <p>
280 * Always returns present <code>TopicPermission</code> actions in the
281 * following order: <code>publish</code>,<code>subscribe</code>.
282 *
283 * @return Canonical string representation of the
284 * <code>TopicPermission</code> actions.
285 */
286 public String getActions() {
287 String result = actions;
288 if (result == null) {
289 StringBuffer sb = new StringBuffer();
290 boolean comma = false;
291 int mask = getActionsMask();
292 if ((mask & ACTION_PUBLISH) == ACTION_PUBLISH) {
293 sb.append(PUBLISH);
294 comma = true;
295 }
296 if ((mask & ACTION_SUBSCRIBE) == ACTION_SUBSCRIBE) {
297 if (comma)
298 sb.append(',');
299 sb.append(SUBSCRIBE);
300 }
301 actions = result = sb.toString();
302 }
303 return result;
304 }
305
306 /**
307 * Returns a new <code>PermissionCollection</code> object suitable for
308 * storing <code>TopicPermission</code> objects.
309 *
310 * @return A new <code>PermissionCollection</code> object.
311 */
312 public PermissionCollection newPermissionCollection() {
313 return new TopicPermissionCollection();
314 }
315
316 /**
317 * Determines the equality of two <code>TopicPermission</code> objects.
318 *
319 * This method checks that specified <code>TopicPermission</code> has the
320 * same topic name and actions as this <code>TopicPermission</code> object.
321 *
322 * @param obj The object to test for equality with this
323 * <code>TopicPermission</code> object.
324 * @return <code>true</code> if <code>obj</code> is a
325 * <code>TopicPermission</code>, and has the same topic name and
326 * actions as this <code>TopicPermission</code> object;
327 * <code>false</code> otherwise.
328 */
329 public boolean equals(Object obj) {
330 if (obj == this) {
331 return true;
332 }
333 if (!(obj instanceof TopicPermission)) {
334 return false;
335 }
336 TopicPermission tp = (TopicPermission) obj;
337 return (getActionsMask() == tp.getActionsMask())
338 && getName().equals(tp.getName());
339 }
340
341 /**
342 * Returns the hash code value for this object.
343 *
344 * @return A hash code value for this object.
345 */
346 public int hashCode() {
347 int h = 31 * 17 + getName().hashCode();
348 h = 31 * h + getActions().hashCode();
349 return h;
350 }
351
352 /**
353 * WriteObject is called to save the state of this permission object to a
354 * stream. The actions are serialized, and the superclass takes care of the
355 * name.
356 */
357 private synchronized void writeObject(java.io.ObjectOutputStream s)
358 throws IOException {
359 // Write out the actions. The superclass takes care of the name
360 // call getActions to make sure actions field is initialized
361 if (actions == null)
362 getActions();
363 s.defaultWriteObject();
364 }
365
366 /**
367 * readObject is called to restore the state of this permission from a
368 * stream.
369 */
370 private synchronized void readObject(java.io.ObjectInputStream s)
371 throws IOException, ClassNotFoundException {
372 // Read in the action, then initialize the rest
373 s.defaultReadObject();
374 setTransients(parseActions(actions));
375 }
376 }
377
378 /**
379 * Stores a set of <code>TopicPermission</code> permissions.
380 *
381 * @see java.security.Permission
382 * @see java.security.Permissions
383 * @see java.security.PermissionCollection
384 */
385 final class TopicPermissionCollection extends PermissionCollection {
386 static final long serialVersionUID = -614647783533924048L;
387 /**
388 * Table of permissions.
389 *
390 * @serial
391 * @GuardedBy this
392 */
393 private final Hashtable permissions;
394 /**
395 * Boolean saying if "*" is in the collection.
396 *
397 * @serial
398 * @GuardedBy this
399 */
400 private boolean all_allowed;
401
402 /**
403 * Create an empty TopicPermissions object.
404 *
405 */
406 public TopicPermissionCollection() {
407 permissions = new Hashtable();
408 all_allowed = false;
409 }
410
411 /**
412 * Adds a permission to the <code>TopicPermission</code> objects. The key
413 * for the hash is the name.
414 *
415 * @param permission The <code>TopicPermission</code> object to add.
416 *
417 * @throws IllegalArgumentException If the permission is not a
418 * <code>TopicPermission</code> instance.
419 *
420 * @throws SecurityException If this <code>TopicPermissionCollection</code>
421 * object has been marked read-only.
422 */
423 public void add(final Permission permission) {
424 if (!(permission instanceof TopicPermission)) {
425 throw new IllegalArgumentException("invalid permission: "
426 + permission);
427 }
428 if (isReadOnly()) {
429 throw new SecurityException("attempt to add a Permission to a "
430 + "readonly PermissionCollection");
431 }
432 final TopicPermission tp = (TopicPermission) permission;
433 final String name = tp.getName();
434 final int newMask = tp.getActionsMask();
435
436 synchronized (this) {
437 final TopicPermission existing = (TopicPermission) permissions
438 .get(name);
439 if (existing != null) {
440 final int oldMask = existing.getActionsMask();
441 if (oldMask != newMask) {
442 permissions.put(name, new TopicPermission(name, oldMask
443 | newMask));
444 }
445 }
446 else {
447 permissions.put(name, tp);
448 }
449 if (!all_allowed) {
450 if (name.equals("*"))
451 all_allowed = true;
452 }
453 }
454 }
455
456 /**
457 * Determines if the specified permissions implies the permissions expressed
458 * in <code>permission</code>.
459 *
460 * @param permission The Permission object to compare with this
461 * <code>TopicPermission</code> object.
462 *
463 * @return <code>true</code> if <code>permission</code> is a proper subset
464 * of a permission in the set; <code>false</code> otherwise.
465 */
466 public boolean implies(final Permission permission) {
467 if (!(permission instanceof TopicPermission)) {
468 return false;
469 }
470 final TopicPermission requested = (TopicPermission) permission;
471 String name = requested.getName();
472 final int desired = requested.getActionsMask();
473 int effective = 0;
474
475 TopicPermission x;
476 // short circuit if the "*" Permission was added
477 synchronized (this) {
478 if (all_allowed) {
479 x = (TopicPermission) permissions.get("*");
480 if (x != null) {
481 effective |= x.getActionsMask();
482 if ((effective & desired) == desired) {
483 return true;
484 }
485 }
486 }
487 x = (TopicPermission) permissions.get(name);
488 }
489 // strategy:
490 // Check for full match first. Then work our way up the
491 // name looking for matches on a/b/*
492 if (x != null) {
493 // we have a direct hit!
494 effective |= x.getActionsMask();
495 if ((effective & desired) == desired) {
496 return true;
497 }
498 }
499 // work our way up the tree...
500 int last;
501 int offset = name.length() - 1;
502 while ((last = name.lastIndexOf("/", offset)) != -1) {
503 name = name.substring(0, last + 1) + "*";
504 synchronized (this) {
505 x = (TopicPermission) permissions.get(name);
506 }
507 if (x != null) {
508 effective |= x.getActionsMask();
509 if ((effective & desired) == desired) {
510 return true;
511 }
512 }
513 offset = last - 1;
514 }
515 // we don't have to check for "*" as it was already checked
516 // at the top (all_allowed), so we just return false
517 return false;
518 }
519
520 /**
521 * Returns an enumeration of all <code>TopicPermission</code> objects in the
522 * container.
523 *
524 * @return Enumeration of all <code>TopicPermission</code> objects.
525 */
526 public Enumeration elements() {
527 return permissions.elements();
528 }
529 }