View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.daemon.support;
19  
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.ArrayList;
24  import java.util.Properties;
25  import java.text.ParseException;
26  
27  /**
28   * Used by jsvc for Daemon configuration.
29   * <p>
30   * Configuration is read from properties file.
31   * If no properties file is given the {@code daemon.properties}
32   * is used from the current directory.
33   * </p>
34   * <p>
35   * The properties file can have property values expanded at runtime
36   * by using System properties or execution environment. The part
37   * of the property value between {@code ${} and {@code }}
38   * will be used as System property or environment key. If found then
39   * the entire {@code ${foo}} will be replaced by the value of
40   * either system property or environment variable named {@code foo}.
41   * </p>
42   * <p>
43   * If no variable is found the {@code ${foo}}  will be passed as is.
44   * In case of {@code $${foo}} this will be unescaped and resulting
45   * value will be {@code ${foo}}.
46   * </p>
47   */
48  public final class DaemonConfiguration
49  {
50      /**
51       * Default configuration file name.
52       */
53      protected final static String DEFAULT_CONFIG        = "daemon.properties";
54      /**
55       * Property prefix
56       */
57      protected final static String PREFIX                = "daemon.";
58      private   final static String BTOKEN                = "${";
59      private   final static String ETOKEN                = "}";
60  
61  
62      private final Properties configurationProperties;
63      private final Properties systemProperties;
64  
65      /**
66       * An empty immutable {@code String} array.
67       */
68      static final String[] EMPTY_STRING_ARRAY = {};
69  
70      /**
71       * Default constructor
72       */
73      public DaemonConfiguration()
74      {
75          configurationProperties = new Properties();
76          systemProperties        = System.getProperties();
77      }
78  
79      /**
80       * Loads the configuration properties file.
81       *
82       * @param fileName The properties file to load.
83       * @return {@code true} if the file was loaded.
84       */
85      public boolean load(String fileName)
86      {
87          if (fileName == null) {
88              fileName = DEFAULT_CONFIG;
89          }
90          
91          try (InputStream inputStream = new FileInputStream(fileName)) {
92              configurationProperties.clear();
93              configurationProperties.load(inputStream);
94              return true;
95          } catch (final IOException ex) {
96              // Error reading properties file
97              return false;
98          }
99      }
100 
101     private String expandProperty(final String propValue)
102         throws ParseException
103     {
104         final StringBuilder expanded;
105         int btoken;
106         int ctoken = 0;
107 
108         if (propValue == null) {
109             return null;
110         }
111         expanded = new StringBuilder();
112         btoken   = propValue.indexOf(BTOKEN);
113         while (btoken != -1) {
114             if (btoken > 0 && propValue.charAt(btoken - 1) == BTOKEN.charAt(0)) {
115                 // Skip and unquote.
116                 expanded.append(propValue.substring(ctoken, btoken));
117                 ctoken = btoken + 1;
118                 btoken = propValue.indexOf(BTOKEN, btoken + BTOKEN.length());
119                 continue;
120             }
121             final int etoken = propValue.indexOf(ETOKEN, btoken);
122             if (etoken == -1) {
123                 // We have "${" without "}"
124                 throw new ParseException("Error while looking for teminating '" +
125                                          ETOKEN + "'", btoken);
126             }
127             final String variable = propValue.substring(btoken + BTOKEN.length(), etoken);
128             String sysvalue = systemProperties.getProperty(variable);
129             if (sysvalue == null) {
130                 // Try with the environment if there was no
131                 // property by that name.
132                 sysvalue = System.getenv(variable);
133             }
134             if (sysvalue != null) {
135                 final String strtoken = propValue.substring(ctoken, btoken);
136                 expanded.append(strtoken);
137                 expanded.append(sysvalue);
138                 ctoken = etoken + ETOKEN.length();
139             }
140             btoken = propValue.indexOf(BTOKEN, etoken + ETOKEN.length());
141         }
142         // Add what's left.
143         expanded.append(propValue.substring(ctoken));
144         return expanded.toString();
145     }
146 
147     /**
148      * Gets the configuration property.
149      *
150      * @param name The name of the property to get.
151      *
152      * @throws ParseException if the property is wrongly formatted.
153      *
154      * @return  Configuration property including any expansion/replacement
155      */
156     public String getProperty(final String name)
157         throws ParseException
158     {
159         if (name == null) {
160             return null;
161         }
162         return expandProperty(configurationProperties.getProperty(PREFIX + name));
163     }
164 
165     /**
166      * Gets the configuration property array.
167      * <p>
168      * Property array is constructed form the list of properties
169      * which end with {@code [index]}
170      * </p>
171      * <pre>
172      * daemon.arg[0] = argument 1
173      * daemon.arg[1] = argument 2
174      * daemon.arg[2] = argument 3
175      * </pre>
176      * @param name The name of the property array to get.
177      *
178      * @throws ParseException if the property is wrongly formatted.
179      *
180      * @return  Configuration property array including any expansion/replacement
181      */
182     public String[] getPropertyArray(final String name)
183         throws ParseException
184     {
185         final ArrayList<String> list = new ArrayList<>();
186         String    args;
187 
188         // Load daemon.arg[0] ... daemon.arg[n] into the String array.
189         //
190         while ((args = getProperty(name + "[" + list.size() + "]")) != null) {
191             list.add(args);
192         }
193         return list.toArray(EMPTY_STRING_ARRAY);
194     }
195 }
196