001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018package org.apache.commons.daemon;
019
020import java.security.Permission;
021import java.util.StringTokenizer;
022
023/**
024 * Represents the permissions to control and query the status of
025 * a {@code Daemon}. A {@code DaemonPermission} consists of a
026 * target name and a list of actions associated with it.
027 * <p>
028 * In this specification version the only available target name for this
029 * permission is &quot;control&quot;, but further releases may add more target
030 * names to fine-tune the access that needs to be granted to the caller.
031 * </p>
032 * <p>
033 * Actions are defined by a string of comma-separated values, as shown in the
034 * table below. The empty string implies no permission at all, while the
035 * special &quot;*&quot; value implies all permissions for the given
036 * name:
037 * </p>
038 * <table border="1">
039 *  <caption>Supported Actions</caption>
040 *  <tr>
041 *   <th>Target&quot;Name</th>
042 *   <th>Action</th>
043 *   <th>Description</th>
044 *  </tr>
045 *  <tr>
046 *   <td rowspan="5">&quot;control&quot;</td>
047 *   <td>&quot;start&quot;</td>
048 *   <td>
049 *    The permission to call the {@code start()} method in an instance
050 *    of a {@code DaemonController} interface.
051 *   </td>
052 *  </tr>
053 *  <tr>
054 *   <td>&quot;stop&quot;</td>
055 *   <td>
056 *    The permission to call the {@code stop()} method in an instance
057 *    of a {@code DaemonController} interface.
058 *   </td>
059 *  </tr>
060 *  <tr>
061 *   <td>&quot;shutdown&quot;</td>
062 *   <td>
063 *    The permission to call the {@code shutdown()} method in an instance
064 *    of a {@code DaemonController} interface.
065 *   </td>
066 *  </tr>
067 *  <tr>
068 *   <td>&quot;reload&quot;</td>
069 *   <td>
070 *    The permission to call the {@code reload()} method in an instance
071 *    of a {@code DaemonController} interface.
072 *   </td>
073 *  </tr>
074 *  <tr>
075 *   <td>&quot;*&quot;</td>
076 *   <td>
077 *    The special wildcard action implies all above-mentioned action. This is
078 *    equal to construct a permission with the &quot;start, stop, shutdown,
079 *    reload&quot; list of actions.
080 *   </td>
081 *  </tr>
082 * </table>
083 */
084public final class DaemonPermission extends Permission
085{
086
087    /* ====================================================================
088     * Constants.
089     */
090
091    private static final long serialVersionUID = -8682149075879731987L;
092
093    /**
094     * The target name when associated with control actions
095     * (&quot;control&quot;).
096     */
097    protected static final String CONTROL = "control";
098
099    /**
100     * The target type when associated with control actions.
101     */
102    protected static final int TYPE_CONTROL = 1;
103
104    /**
105     * The action name associated with the permission to call the
106     * {@code DaemonController.start()} method.
107     */
108    protected static final String CONTROL_START = "start";
109
110    /**
111     * The action name associated with the permission to call the
112     * {@code DaemonController.stop()} method.
113     */
114    protected static final String CONTROL_STOP = "stop";
115
116    /**
117     * The action name associated with the permission to call the
118     * {@code DaemonController.shutdown()} method.
119     */
120    protected static final String CONTROL_SHUTDOWN = "shutdown";
121
122    /**
123     * The action name associated with the permission to call the
124     * {@code DaemonController.reload()} method.
125     */
126    protected static final String CONTROL_RELOAD = "reload";
127
128    /**
129     * The action mask associated with the permission to call the
130     * {@code DaemonController.start()} method.
131     */
132    protected static final int MASK_CONTROL_START = 0x01;
133
134    /**
135     * The action mask associated with the permission to call the
136     * {@code DaemonController.stop()} method.
137     */
138    protected static final int MASK_CONTROL_STOP = 0x02;
139
140    /**
141     * The action mask associated with the permission to call the
142     * {@code DaemonController.shutdown()} method.
143     */
144    protected static final int MASK_CONTROL_SHUTDOWN = 0x04;
145
146    /**
147     * The action mask associated with the permission to call the
148     * {@code DaemonController.reload()} method.
149     */
150    protected static final int MASK_CONTROL_RELOAD = 0x08;
151
152    /**
153     * The &quot;wildcard&quot; action implying all actions for the given
154     * target name.
155     */
156    protected static final String WILDCARD = "*";
157
158    /* ====================================================================
159     * Instance variables
160     */
161
162    /** The type of this permission object. */
163    private transient int type;
164    /** The permission mask associated with this permission object. */
165    private transient int mask;
166    /** The String representation of this permission object. */
167    private transient String desc;
168
169    /* ====================================================================
170     * Constructors
171     */
172
173    /**
174     * Creates a new {@code DaemonPermission} instance with a specified
175     * permission name.
176     * <p>
177     * This constructor will create a new {@code DaemonPermission}
178     * instance that <b>will not</b> grant any permission to the caller.
179     *
180     * @param target The target name of this permission.
181     * @throws IllegalArgumentException If the specified target name is not
182     *                supported.
183     */
184    public DaemonPermission(final String target)
185        throws IllegalArgumentException
186    {
187        // Set up the target name of this permission object.
188        super(target);
189
190        // Check if the permission target name was specified
191        if (target == null) {
192            throw new IllegalArgumentException("Null permission name");
193        }
194
195        // Check if this is a "control" permission and set up accordingly.
196        if (CONTROL.equalsIgnoreCase(target)) {
197            type = TYPE_CONTROL;
198            return;
199        }
200
201        // If we got here, we have an invalid permission name.
202        throw new IllegalArgumentException("Invalid permission name \"" +
203                                           target + "\" specified");
204    }
205
206    /**
207     * Creates a new {@code DaemonPermission} instance with a specified
208     * permission name and a specified list of actions.
209     *
210     * @param target The target name of this permission.
211     * @param actions The list of actions permitted by this permission.
212     * @throws IllegalArgumentException If the specified target name is not
213     *                supported, or the specified list of actions includes an
214     *                invalid value.
215     */
216    public DaemonPermission(final String target, final String actions)
217        throws IllegalArgumentException
218    {
219        // Setup this instance's target name.
220        this(target);
221
222        // Create the appropriate mask if this is a control permission.
223        if (this.type == TYPE_CONTROL) {
224            this.mask = this.createControlMask(actions);
225        }
226    }
227
228    /* ====================================================================
229     * Public methods
230     */
231
232    /**
233     * Returns the list of actions permitted by this instance of
234     * {@code DaemonPermission} in its canonical form.
235     *
236     * @return The canonicalized list of actions.
237     */
238    @Override
239    public String getActions()
240    {
241        if (this.type == TYPE_CONTROL) {
242            return this.createControlActions(this.mask);
243        }
244        return "";
245    }
246
247    /**
248     * Returns the hash code for this {@code DaemonPermission} instance.
249     *
250     * @return An hash code value.
251     */
252    @Override
253    public int hashCode()
254    {
255        this.setupDescription();
256        return this.desc.hashCode();
257    }
258
259    /**
260     * Checks if a specified object equals {@code DaemonPermission}.
261     *
262     * @return <b>true</b> or <b>false</b> whether the specified object equals
263     *         this {@code DaemonPermission} instance or not.
264     */
265    @Override
266    public boolean equals(final Object object)
267    {
268        if (object == this) {
269            return true;
270        }
271
272        if (!(object instanceof DaemonPermission)) {
273            return false;
274        }
275
276        final DaemonPermission that = (DaemonPermission) object;
277
278        if (this.type != that.type) {
279            return false;
280        }
281        return this.mask == that.mask;
282    }
283
284    /**
285     * Checks if this {@code DaemonPermission} implies another
286     * {@code Permission}.
287     *
288     * @return <b>true</b> or <b>false</b> whether the specified permission
289     *         is implied by this {@code DaemonPermission} instance or
290     *         not.
291     */
292    @Override
293    public boolean implies(final Permission permission)
294    {
295        if (permission == this) {
296            return true;
297        }
298
299        if (!(permission instanceof DaemonPermission)) {
300            return false;
301        }
302
303        final DaemonPermission that = (DaemonPermission) permission;
304
305        if (this.type != that.type) {
306            return false;
307        }
308        return (this.mask & that.mask) == that.mask;
309    }
310
311    /**
312     * Returns a {@code String} representation of this instance.
313     *
314     * @return A {@code String} representing this
315     *         {@code DaemonPermission} instance.
316     */
317    @Override
318    public String toString()
319    {
320        this.setupDescription();
321        return this.desc;
322    }
323
324    /* ====================================================================
325     * Private methods
326     */
327
328    /**
329     * Creates a String description for this permission instance.
330     */
331    private void setupDescription()
332    {
333        if (this.desc != null) {
334            return;
335        }
336
337        final StringBuilder buf = new StringBuilder();
338        buf.append(this.getClass().getName());
339        buf.append('[');
340        switch (this.type) {
341            case TYPE_CONTROL:
342                buf.append(CONTROL);
343            break;
344            default:
345                buf.append("UNKNOWN");
346            break;
347        }
348        buf.append(':');
349        buf.append(this.getActions());
350        buf.append(']');
351
352        this.desc = buf.toString();
353    }
354
355    /**
356     * Creates a permission mask for a given control actions string.
357     */
358    private int createControlMask(final String actions)
359        throws IllegalArgumentException
360    {
361        if (actions == null) {
362            return 0;
363        }
364
365        int mask = 0;
366        final StringTokenizer tok = new StringTokenizer(actions, ",", false);
367
368        while (tok.hasMoreTokens()) {
369            final String val = tok.nextToken().trim();
370
371            if (WILDCARD.equals(val)) {
372                return MASK_CONTROL_START | MASK_CONTROL_STOP |
373                       MASK_CONTROL_SHUTDOWN | MASK_CONTROL_RELOAD;
374            }
375            if (CONTROL_START.equalsIgnoreCase(val)) {
376                mask = mask | MASK_CONTROL_START;
377            }
378            else if (CONTROL_STOP.equalsIgnoreCase(val)) {
379                mask = mask | MASK_CONTROL_STOP;
380            }
381            else if (CONTROL_SHUTDOWN.equalsIgnoreCase(val)) {
382                mask = mask | MASK_CONTROL_SHUTDOWN;
383            }
384            else if (CONTROL_RELOAD.equalsIgnoreCase(val)) {
385                mask = mask | MASK_CONTROL_RELOAD;
386            }
387            else {
388                throw new IllegalArgumentException("Invalid action name \"" +
389                                                   val + "\" specified");
390            }
391        }
392        return mask;
393    }
394
395    /** Creates an actions list for a given control permission mask. */
396    private String createControlActions(final int mask)
397    {
398        final StringBuilder buf = new StringBuilder();
399        boolean sep = false;
400
401        if ((mask & MASK_CONTROL_START) == MASK_CONTROL_START) {
402            sep = true;
403            buf.append(CONTROL_START);
404        }
405
406        if ((mask & MASK_CONTROL_STOP) == MASK_CONTROL_STOP) {
407            if (sep) {
408                buf.append(",");
409            }
410            else {
411                sep = true;
412            }
413            buf.append(CONTROL_STOP);
414        }
415
416        if ((mask & MASK_CONTROL_SHUTDOWN) == MASK_CONTROL_SHUTDOWN) {
417            if (sep) {
418                buf.append(",");
419            }
420            else {
421                sep = true;
422            }
423            buf.append(CONTROL_SHUTDOWN);
424        }
425
426        if ((mask & MASK_CONTROL_RELOAD) == MASK_CONTROL_RELOAD) {
427            if (sep) {
428                buf.append(",");
429            }
430            else {
431                sep = true;
432            }
433            buf.append(CONTROL_RELOAD);
434        }
435
436        return buf.toString();
437    }
438}
439