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    *      https://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      /**
52       * Default configuration file name.
53       */
54      protected static final String DEFAULT_CONFIG        = "daemon.properties";
55  
56      /**
57       * Property prefix
58       */
59      protected static final String PREFIX                = "daemon.";
60      private   static final String BTOKEN                = "${";
61      private   static final String ETOKEN                = "}";
62  
63      private final Properties configurationProperties;
64      private final Properties systemProperties;
65  
66      /**
67       * An empty immutable {@code String} array.
68       */
69      static final String[] EMPTY_STRING_ARRAY = {};
70  
71      /**
72       * Default constructor
73       */
74      public DaemonConfiguration()
75      {
76          configurationProperties = new Properties();
77          systemProperties        = System.getProperties();
78      }
79  
80      /**
81       * Loads the configuration properties file.
82       *
83       * @param fileName The properties file to load.
84       * @return {@code true} if the file was loaded.
85       */
86      public boolean load(String fileName)
87      {
88          if (fileName == null) {
89              fileName = DEFAULT_CONFIG;
90          }
91  
92          try (InputStream inputStream = new FileInputStream(fileName)) {
93              configurationProperties.clear();
94              configurationProperties.load(inputStream);
95              return true;
96          } catch (final IOException ex) {
97              // Error reading properties file
98              return false;
99          }
100     }
101 
102     private String expandProperty(final String propValue)
103         throws ParseException
104     {
105         final StringBuilder expanded;
106         int btoken;
107         int ctoken = 0;
108 
109         if (propValue == null) {
110             return null;
111         }
112         expanded = new StringBuilder();
113         btoken   = propValue.indexOf(BTOKEN);
114         while (btoken != -1) {
115             if (btoken > 0 && propValue.charAt(btoken - 1) == BTOKEN.charAt(0)) {
116                 // Skip and unquote.
117                 expanded.append(propValue.substring(ctoken, btoken));
118                 ctoken = btoken + 1;
119                 btoken = propValue.indexOf(BTOKEN, btoken + BTOKEN.length());
120                 continue;
121             }
122             final int etoken = propValue.indexOf(ETOKEN, btoken);
123             if (etoken == -1) {
124                 // We have "${" without "}"
125                 throw new ParseException("Error while looking for teminating '" +
126                                          ETOKEN + "'", btoken);
127             }
128             final String variable = propValue.substring(btoken + BTOKEN.length(), etoken);
129             String sysvalue = systemProperties.getProperty(variable);
130             if (sysvalue == null) {
131                 // Try with the environment if there was no
132                 // property by that name.
133                 sysvalue = System.getenv(variable);
134             }
135             if (sysvalue != null) {
136                 final String strtoken = propValue.substring(ctoken, btoken);
137                 expanded.append(strtoken);
138                 expanded.append(sysvalue);
139                 ctoken = etoken + ETOKEN.length();
140             }
141             btoken = propValue.indexOf(BTOKEN, etoken + ETOKEN.length());
142         }
143         // Add what's left.
144         expanded.append(propValue.substring(ctoken));
145         return expanded.toString();
146     }
147 
148     /**
149      * Gets the configuration property.
150      *
151      * @param name The name of the property to get.
152      * @throws ParseException if the property is wrongly formatted.
153      * @return  Configuration property including any expansion/replacement
154      */
155     public String getProperty(final String name)
156         throws ParseException
157     {
158         if (name == null) {
159             return null;
160         }
161         return expandProperty(configurationProperties.getProperty(PREFIX + name));
162     }
163 
164     /**
165      * Gets the configuration property array.
166      * <p>
167      * Property array is constructed form the list of properties
168      * which end with {@code [index]}
169      * </p>
170      * <pre>
171      * daemon.arg[0] = argument 1
172      * daemon.arg[1] = argument 2
173      * daemon.arg[2] = argument 3
174      * </pre>
175      *
176      * @param name The name of the property array to get.
177      * @throws ParseException if the property is wrongly formatted.
178      * @return  Configuration property array including any expansion/replacement
179      */
180     public String[] getPropertyArray(final String name)
181         throws ParseException
182     {
183         final ArrayList<String> list = new ArrayList<>();
184         String    args;
185 
186         // Load daemon.arg[0] ... daemon.arg[n] into the String array.
187         //
188         while ((args = getProperty(name + "[" + list.size() + "]")) != null) {
189             list.add(args);
190         }
191         return list.toArray(EMPTY_STRING_ARRAY);
192     }
193 }
194