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