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