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