blob: 8bb2fe08d0f8f5be102f8b772f6ce8e2d4f4e9f8 [file] [log] [blame]
Christian van Spaandonk63814412008-08-02 09:56:01 +00001/*
Richard S. Hall8df9ab12009-07-24 17:06:37 +00002 * Copyright (c) OSGi Alliance (2005, 2008). All Rights Reserved.
Christian van Spaandonk63814412008-08-02 09:56:01 +00003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.osgi.service.monitor;
18
19import java.io.UnsupportedEncodingException;
20import java.security.Permission;
21import java.util.StringTokenizer;
22
23/**
24 * Indicates the callers authority to publish, read or reset
Richard S. Hall8df9ab12009-07-24 17:06:37 +000025 * <code>StatusVariable</code>s, to switch event sending on or off or to start
26 * monitoring jobs. The target of the permission is the identifier of the
Christian van Spaandonk63814412008-08-02 09:56:01 +000027 * <code>StatusVariable</code>, the action can be <code>read</code>,
28 * <code>publish</code>, <code>reset</code>, <code>startjob</code>,
Richard S. Hall8df9ab12009-07-24 17:06:37 +000029 * <code>switchevents</code>, or the combination of these separated by commas.
30 * Action names are interpreted case-insensitively, but the canonical action
31 * string returned by {@link #getActions} uses the forms defined by the action
32 * constants.
Christian van Spaandonk63814412008-08-02 09:56:01 +000033 * <p>
Richard S. Hall8df9ab12009-07-24 17:06:37 +000034 * If the wildcard <code>*</code> appears in the actions field, all legal
35 * monitoring commands are allowed on the designated target(s) by the owner of
Christian van Spaandonk63814412008-08-02 09:56:01 +000036 * the permission.
Richard S. Hall8df9ab12009-07-24 17:06:37 +000037 *
Richard S. Halla3617582009-09-01 13:57:57 +000038 * @version $Revision: 7941 $
Christian van Spaandonk63814412008-08-02 09:56:01 +000039 */
40public class MonitorPermission extends Permission {
41
42 /**
43 *
44 */
45 private static final long serialVersionUID = -9084425194463274314L;
46
47 /**
48 * Holders of <code>MonitorPermission</code> with the <code>read</code>
49 * action present are allowed to read the value of the
50 * <code>StatusVariable</code>s specified in the permission's target field.
51 */
52 public static final String READ = "read";
53
54 /**
55 * Holders of <code>MonitorPermission</code> with the <code>reset</code>
56 * action present are allowed to reset the value of the
57 * <code>StatusVariable</code>s specified in the permission's target field.
58 */
59 public static final String RESET = "reset";
60
61 /**
62 * Holders of <code>MonitorPermission</code> with the <code>publish</code>
63 * action present are <code>Monitorable</code> services that are allowed
64 * to publish the <code>StatusVariable</code>s specified in the
65 * permission's target field. Note, that this permission cannot be enforced
66 * when a <code>Monitorable</code> registers to the framework, because the
67 * Service Registry does not know about this permission. Instead, any
68 * <code>StatusVariable</code>s published by a <code>Monitorable</code>
69 * without the corresponding <code>publish</code> permission are silently
70 * ignored by <code>MonitorAdmin</code>, and are therefore invisible to the
71 * users of the monitoring service.
72 */
73 public static final String PUBLISH = "publish";
74
75 /**
76 * Holders of <code>MonitorPermission</code> with the <code>startjob</code>
77 * action present are allowed to initiate monitoring jobs involving the
78 * <code>StatusVariable</code>s specified in the permission's target field.
79 * <p>
80 * A minimal sampling interval can be optionally defined in the following
81 * form: <code>startjob:n</code>. This allows the holder of the permission
82 * to initiate time based jobs with a measurement interval of at least
83 * <code>n</code> seconds. If <code>n</code> is not specified or 0 then the
84 * holder of this permission is allowed to start monitoring jobs specifying
85 * any frequency.
86 */
87 public static final String STARTJOB = "startjob";
88
89 /**
90 * Holders of <code>MonitorPermission</code> with the
91 * <code>switchevents</code> action present are allowed to switch event
92 * sending on or off for the value of the <code>StatusVariable</code>s
93 * specified in the permission's target field.
94 */
95 public static final String SWITCHEVENTS = "switchevents";
96
97 private static final int READ_FLAG = 0x1;
98 private static final int RESET_FLAG = 0x2;
99 private static final int PUBLISH_FLAG = 0x4;
100 private static final int STARTJOB_FLAG = 0x8;
101 private static final int SWITCHEVENTS_FLAG = 0x10;
102
103 private static final int ALL_FLAGS = READ_FLAG | RESET_FLAG |
104 PUBLISH_FLAG | STARTJOB_FLAG | SWITCHEVENTS_FLAG;
105
106 private String monId;
107 private String varId;
108 private boolean prefixMonId;
109 private boolean prefixVarId;
110 private int mask;
111 private int minJobInterval;
112
113 /**
114 * Create a <code>MonitorPermission</code> object, specifying the target
115 * and actions.
116 * <p>
117 * The <code>statusVariable</code> parameter is the target of the
118 * permission, defining one or more status variable names to which the
119 * specified actions apply. Multiple status variable names can be selected
120 * by using the wildcard <code>*</code> in the target string. The wildcard
121 * is allowed in both fragments, but only at the end of the fragments.
122 * <p>
123 * For example, the following targets are valid:
124 * <code>com.mycomp.myapp/queue_length</code>,
125 * <code>com.mycomp.myapp/*</code>, <code>com.mycomp.&#42;/*</code>,
126 * <code>&#42;/*</code>, <code>&#42;/queue_length</code>,
127 * <code>&#42;/queue*</code>.
128 * <p>
129 * The following targets are invalid:
130 * <code>*.myapp/queue_length</code>, <code>com.*.myapp/*</code>,
131 * <code>*</code>.
132 * <p>
133 * The <code>actions</code> parameter specifies the allowed action(s):
134 * <code>read</code>, <code>publish</code>, <code>startjob</code>,
135 * <code>reset</code>, <code>switchevents</code>, or the combination of
136 * these separated by commas. String constants are defined in this class for
137 * each valid action. Passing <code>&quot;*&quot;</code> as the action
138 * string is equivalent to listing all actions.
139 *
140 * @param statusVariable the identifier of the <code>StatusVariable</code>
141 * in [Monitorable_id]/[StatusVariable_id] format
142 * @param actions the list of allowed actions separated by commas, or
143 * <code>*</code> for all actions
144 * @throws java.lang.IllegalArgumentException if either parameter is
145 * <code>null</code>, or invalid with regard to the constraints
146 * defined above and in the documentation of the used actions
147 */
148 public MonitorPermission(String statusVariable, String actions)
149 throws IllegalArgumentException {
150 super(statusVariable);
151
152 if(statusVariable == null)
153 throw new IllegalArgumentException(
154 "Invalid StatusVariable path 'null'.");
155
156 if(actions == null)
157 throw new IllegalArgumentException(
158 "Invalid actions string 'null'.");
159
160 int sep = statusVariable.indexOf('/');
161 int len = statusVariable.length();
162
163 if (sep == -1)
164 throw new IllegalArgumentException(
165 "Invalid StatusVariable path: should contain '/' separator.");
166 if (sep == 0 || sep == statusVariable.length() - 1)
167 throw new IllegalArgumentException(
168 "Invalid StatusVariable path: empty monitorable ID or StatusVariable name.");
169
170 prefixMonId = statusVariable.charAt(sep - 1) == '*';
171 prefixVarId = statusVariable.charAt(len - 1) == '*';
172
173 monId = statusVariable.substring(0, prefixMonId ? sep - 1 : sep);
174 varId = statusVariable.substring(sep + 1, prefixVarId ? len - 1 : len);
175
176 checkId(monId, "Monitorable ID part of the target");
177 checkId(varId, "Status Variable ID part of the target");
178
179 minJobInterval = 0;
180
181 if(actions.equals("*"))
182 mask = ALL_FLAGS;
183 else {
184 mask = 0;
185 StringTokenizer st = new StringTokenizer(actions, ",");
186 while (st.hasMoreTokens()) {
Richard S. Halla3617582009-09-01 13:57:57 +0000187 String action = st.nextToken().trim();
Christian van Spaandonk63814412008-08-02 09:56:01 +0000188 if (action.equalsIgnoreCase(READ)) {
189 addToMask(READ_FLAG, READ);
190 } else if (action.equalsIgnoreCase(RESET)) {
191 addToMask(RESET_FLAG, RESET);
192 } else if (action.equalsIgnoreCase(PUBLISH)) {
193 addToMask(PUBLISH_FLAG, PUBLISH);
194 } else if (action.equalsIgnoreCase(SWITCHEVENTS)) {
195 addToMask(SWITCHEVENTS_FLAG, SWITCHEVENTS);
196 } else if (action.toLowerCase().startsWith(STARTJOB)) {
197 minJobInterval = 0;
198
199 int slen = STARTJOB.length();
200 if (action.length() != slen) {
201 if (action.charAt(slen) != ':')
202 throw new IllegalArgumentException(
203 "Invalid action '" + action + "'.");
204
205 try {
206 minJobInterval = Integer.parseInt(action
207 .substring(slen + 1));
208 } catch (NumberFormatException e) {
209 throw new IllegalArgumentException(
210 "Invalid parameter in startjob action '"
211 + action + "'.");
212 }
213 }
214 addToMask(STARTJOB_FLAG, STARTJOB);
215 } else
216 throw new IllegalArgumentException("Invalid action '" +
217 action + "'");
218 }
219 }
220 }
221
222 private void addToMask(int action, String actionString) {
223 if((mask & action) != 0)
224 throw new IllegalArgumentException("Invalid action string: " +
225 actionString + " appears multiple times.");
226
227 mask |= action;
228 }
229
230 private void checkId(String id, String idName)
231 throws IllegalArgumentException {
232
233 byte[] nameBytes;
234 try {
235 nameBytes = id.getBytes("UTF-8");
236 } catch (UnsupportedEncodingException e) {
237 // never happens, "UTF-8" must always be supported
238 throw new IllegalStateException(e.getMessage());
239 }
240 if(nameBytes.length > StatusVariable.MAX_ID_LENGTH)
241 throw new IllegalArgumentException(idName + " is too long (over " +
242 StatusVariable.MAX_ID_LENGTH + " bytes in UTF-8 encoding).");
243
244
245 if (id.equals(".") || id.equals(".."))
246 throw new IllegalArgumentException(idName + " is invalid.");
247
248 char[] chars = id.toCharArray();
249 for (int i = 0; i < chars.length; i++)
250 if (StatusVariable.SYMBOLIC_NAME_CHARACTERS.indexOf(chars[i]) == -1)
251 throw new IllegalArgumentException(idName +
252 " contains invalid characters.");
253 }
254
255 /**
256 * Create an integer hash of the object. The hash codes of
257 * <code>MonitorPermission</code>s <code>p1</code> and <code>p2</code> are
258 * the same if <code>p1.equals(p2)</code>.
259 *
260 * @return the hash of the object
261 */
262 public int hashCode() {
263 return new Integer(mask).hashCode()
264 ^ new Integer(minJobInterval).hashCode() ^ monId.hashCode()
265 ^ new Boolean(prefixMonId).hashCode()
266 ^ varId.hashCode()
267 ^ new Boolean(prefixVarId).hashCode();
268 }
269
270 /**
271 * Determines the equality of two <code>MonitorPermission</code> objects.
272 * Two <code>MonitorPermission</code> objects are equal if their target
273 * strings are equal and the same set of actions are listed in their action
274 * strings.
275 *
276 * @param o the object being compared for equality with this object
277 * @return <code>true</code> if the two permissions are equal
278 */
279 public boolean equals(Object o) {
280 if (!(o instanceof MonitorPermission))
281 return false;
282
283 MonitorPermission other = (MonitorPermission) o;
284
285 return mask == other.mask && minJobInterval == other.minJobInterval
286 && monId.equals(other.monId)
287 && prefixMonId == other.prefixMonId
288 && varId.equals(other.varId)
289 && prefixVarId == other.prefixVarId;
290 }
291
292 /**
293 * Get the action string associated with this permission. The actions are
294 * returned in the following order: <code>read</code>, <code>reset</code>,
295 * <code>publish</code>, <code>startjob</code>, <code>switchevents</code>.
296 *
297 * @return the allowed actions separated by commas, cannot be
298 * <code>null</code>
299 */
300 public String getActions() {
301 StringBuffer sb = new StringBuffer();
302
303 appendAction(sb, READ_FLAG, READ);
304 appendAction(sb, RESET_FLAG, RESET);
305 appendAction(sb, PUBLISH_FLAG, PUBLISH);
306 appendAction(sb, STARTJOB_FLAG, STARTJOB);
307 appendAction(sb, SWITCHEVENTS_FLAG, SWITCHEVENTS);
308
309 return sb.toString();
310 }
311
312 private void appendAction(StringBuffer sb, int flag, String actionName) {
313 if ((mask & flag) != 0) {
314 if(sb.length() != 0)
315 sb.append(',');
316 sb.append(actionName);
317
318 if(flag == STARTJOB_FLAG && minJobInterval != 0)
319 sb.append(':').append(minJobInterval);
320 }
321 }
322
323 /**
324 * Determines if the specified permission is implied by this permission.
325 * <p>
326 * This method returns <code>false</code> if and only if at least one of the
327 * following conditions are fulfilled for the specified permission:
328 * <ul>
329 * <li>it is not a <code>MonitorPermission</code>
330 * <li>it has a broader set of actions allowed than this one
331 * <li>it allows initiating time based monitoring jobs with a lower minimal
332 * sampling interval
333 * <li>the target set of <code>Monitorable</code>s is not the same nor a
334 * subset of the target set of <code>Monitorable</code>s of this permission
335 * <li>the target set of <code>StatusVariable</code>s is not the same
336 * nor a subset of the target set of <code>StatusVariable</code>s of this
337 * permission
338 * </ul>
339 *
340 * @param p the permission to be checked
341 * @return <code>true</code> if the given permission is implied by this
342 * permission
343 */
344 public boolean implies(Permission p) {
345 if (!(p instanceof MonitorPermission))
346 return false;
347
348 MonitorPermission other = (MonitorPermission) p;
349
350 if ((mask & other.mask) != other.mask)
351 return false;
352
353 if ((other.mask & STARTJOB_FLAG) != 0
354 && minJobInterval > other.minJobInterval)
355 return false;
356
357 return implies(monId, prefixMonId, other.monId, other.prefixMonId)
358 && implies(varId, prefixVarId, other.varId, other.prefixVarId);
359 }
360
361 private boolean implies(String id, boolean prefix, String oid,
362 boolean oprefix) {
363
364 return prefix ? oid.startsWith(id) : !oprefix && id.equals(oid);
365 }
366}