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 *      https://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    private static final long serialVersionUID = -8682149075879731987L;
088
089    /**
090     * The target name when associated with control actions
091     * (&quot;control&quot;).
092     */
093    protected static final String CONTROL = "control";
094
095    /**
096     * The target type when associated with control actions.
097     */
098    protected static final int TYPE_CONTROL = 1;
099
100    /**
101     * The action name associated with the permission to call the
102     * {@code DaemonController.start()} method.
103     */
104    protected static final String CONTROL_START = "start";
105
106    /**
107     * The action name associated with the permission to call the
108     * {@code DaemonController.stop()} method.
109     */
110    protected static final String CONTROL_STOP = "stop";
111
112    /**
113     * The action name associated with the permission to call the
114     * {@code DaemonController.shutdown()} method.
115     */
116    protected static final String CONTROL_SHUTDOWN = "shutdown";
117
118    /**
119     * The action name associated with the permission to call the
120     * {@code DaemonController.reload()} method.
121     */
122    protected static final String CONTROL_RELOAD = "reload";
123
124    /**
125     * The action mask associated with the permission to call the
126     * {@code DaemonController.start()} method.
127     */
128    protected static final int MASK_CONTROL_START = 0x01;
129
130    /**
131     * The action mask associated with the permission to call the
132     * {@code DaemonController.stop()} method.
133     */
134    protected static final int MASK_CONTROL_STOP = 0x02;
135
136    /**
137     * The action mask associated with the permission to call the
138     * {@code DaemonController.shutdown()} method.
139     */
140    protected static final int MASK_CONTROL_SHUTDOWN = 0x04;
141
142    /**
143     * The action mask associated with the permission to call the
144     * {@code DaemonController.reload()} method.
145     */
146    protected static final int MASK_CONTROL_RELOAD = 0x08;
147
148    /**
149     * The &quot;wildcard&quot; action implying all actions for the given
150     * target name.
151     */
152    protected static final String WILDCARD = "*";
153
154    /** The type of this permission object. */
155    private transient int type;
156
157    /** The permission mask associated with this permission object. */
158    private transient int mask;
159
160    /** The String representation of this permission object. */
161    private transient String desc;
162
163    /**
164     * Creates a new {@code DaemonPermission} instance with a specified
165     * permission name.
166     * <p>
167     * This constructor will create a new {@code DaemonPermission}
168     * instance that <strong>will not</strong> grant any permission to the caller.
169     *
170     * @param target The target name of this permission.
171     * @throws IllegalArgumentException If the specified target name is not
172     *                supported.
173     */
174    public DaemonPermission(final String target)
175        throws IllegalArgumentException
176    {
177        // Set up the target name of this permission object.
178        super(target);
179
180        // Check if the permission target name was specified
181        if (target == null) {
182            throw new IllegalArgumentException("Null permission name");
183        }
184
185        // Check if this is a "control" permission and set up accordingly.
186        if (CONTROL.equalsIgnoreCase(target)) {
187            type = TYPE_CONTROL;
188            return;
189        }
190
191        // If we got here, we have an invalid permission name.
192        throw new IllegalArgumentException("Invalid permission name \"" +
193                                           target + "\" specified");
194    }
195
196    /**
197     * Creates a new {@code DaemonPermission} instance with a specified
198     * permission name and a specified list of actions.
199     *
200     * @param target The target name of this permission.
201     * @param actions The list of actions permitted by this permission.
202     * @throws IllegalArgumentException If the specified target name is not
203     *                supported, or the specified list of actions includes an
204     *                invalid value.
205     */
206    public DaemonPermission(final String target, final String actions)
207        throws IllegalArgumentException
208    {
209        // Setup this instance's target name.
210        this(target);
211
212        // Create the appropriate mask if this is a control permission.
213        if (this.type == TYPE_CONTROL) {
214            this.mask = createControlMask(actions);
215        }
216    }
217
218    /**
219     * Returns the list of actions permitted by this instance of
220     * {@code DaemonPermission} in its canonical form.
221     *
222     * @return The canonicalized list of actions.
223     */
224    @Override
225    public String getActions()
226    {
227        if (this.type == TYPE_CONTROL) {
228            return createControlActions(this.mask);
229        }
230        return "";
231    }
232
233    /**
234     * Returns the hash code for this {@code DaemonPermission} instance.
235     *
236     * @return An hash code value.
237     */
238    @Override
239    public int hashCode()
240    {
241        setupDescription();
242        return this.desc.hashCode();
243    }
244
245    /**
246     * Checks if a specified object equals {@code DaemonPermission}.
247     *
248     * @return <strong>true</strong> or <strong>false</strong> whether the specified object equals
249     *         this {@code DaemonPermission} instance or not.
250     */
251    @Override
252    public boolean equals(final Object object)
253    {
254        if (object == this) {
255            return true;
256        }
257
258        if (!(object instanceof DaemonPermission)) {
259            return false;
260        }
261
262        final DaemonPermission that = (DaemonPermission) object;
263
264        if (this.type != that.type) {
265            return false;
266        }
267        return this.mask == that.mask;
268    }
269
270    /**
271     * Checks if this {@code DaemonPermission} implies another
272     * {@code Permission}.
273     *
274     * @return <strong>true</strong> or <strong>false</strong> whether the specified permission
275     *         is implied by this {@code DaemonPermission} instance or
276     *         not.
277     */
278    @Override
279    public boolean implies(final Permission permission)
280    {
281        if (permission == this) {
282            return true;
283        }
284
285        if (!(permission instanceof DaemonPermission)) {
286            return false;
287        }
288
289        final DaemonPermission that = (DaemonPermission) permission;
290
291        if (this.type != that.type) {
292            return false;
293        }
294        return (this.mask & that.mask) == that.mask;
295    }
296
297    /**
298     * Returns a {@code String} representation of this instance.
299     *
300     * @return A {@code String} representing this
301     *         {@code DaemonPermission} instance.
302     */
303    @Override
304    public String toString()
305    {
306        setupDescription();
307        return this.desc;
308    }
309
310    /**
311     * Creates a String description for this permission instance.
312     */
313    private void setupDescription()
314    {
315        if (this.desc != null) {
316            return;
317        }
318
319        final StringBuilder buf = new StringBuilder();
320        buf.append(this.getClass().getName());
321        buf.append('[');
322        switch (this.type) {
323            case TYPE_CONTROL:
324                buf.append(CONTROL);
325            break;
326            default:
327                buf.append("UNKNOWN");
328            break;
329        }
330        buf.append(':');
331        buf.append(getActions());
332        buf.append(']');
333
334        this.desc = buf.toString();
335    }
336
337    /**
338     * Creates a permission mask for a given control actions string.
339     */
340    private int createControlMask(final String actions)
341        throws IllegalArgumentException
342    {
343        if (actions == null) {
344            return 0;
345        }
346
347        int mask = 0;
348        final StringTokenizer tok = new StringTokenizer(actions, ",", false);
349
350        while (tok.hasMoreTokens()) {
351            final String val = tok.nextToken().trim();
352
353            if (WILDCARD.equals(val)) {
354                return MASK_CONTROL_START | MASK_CONTROL_STOP |
355                       MASK_CONTROL_SHUTDOWN | MASK_CONTROL_RELOAD;
356            }
357            if (CONTROL_START.equalsIgnoreCase(val)) {
358                mask |= MASK_CONTROL_START;
359            }
360            else if (CONTROL_STOP.equalsIgnoreCase(val)) {
361                mask |= MASK_CONTROL_STOP;
362            }
363            else if (CONTROL_SHUTDOWN.equalsIgnoreCase(val)) {
364                mask |= MASK_CONTROL_SHUTDOWN;
365            }
366            else if (CONTROL_RELOAD.equalsIgnoreCase(val)) {
367                mask |= MASK_CONTROL_RELOAD;
368            }
369            else {
370                throw new IllegalArgumentException("Invalid action name \"" +
371                                                   val + "\" specified");
372            }
373        }
374        return mask;
375    }
376
377    /** Creates an actions list for a given control permission mask. */
378    private String createControlActions(final int mask)
379    {
380        final StringBuilder buf = new StringBuilder();
381        boolean sep = false;
382
383        if ((mask & MASK_CONTROL_START) == MASK_CONTROL_START) {
384            sep = true;
385            buf.append(CONTROL_START);
386        }
387
388        if ((mask & MASK_CONTROL_STOP) == MASK_CONTROL_STOP) {
389            if (sep) {
390                buf.append(",");
391            }
392            else {
393                sep = true;
394            }
395            buf.append(CONTROL_STOP);
396        }
397
398        if ((mask & MASK_CONTROL_SHUTDOWN) == MASK_CONTROL_SHUTDOWN) {
399            if (sep) {
400                buf.append(",");
401            }
402            else {
403                sep = true;
404            }
405            buf.append(CONTROL_SHUTDOWN);
406        }
407
408        if ((mask & MASK_CONTROL_RELOAD) == MASK_CONTROL_RELOAD) {
409            if (sep) {
410                buf.append(",");
411            }
412            else {
413                sep = true;
414            }
415            buf.append(CONTROL_RELOAD);
416        }
417
418        return buf.toString();
419    }
420}
421