001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018package org.apache.commons.daemon.support;
019
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.ArrayList;
024import java.util.Properties;
025import java.text.ParseException;
026
027/**
028 * Used by jsvc for Daemon configuration.
029 * <p>
030 * Configuration is read from properties file.
031 * If no properties file is given the {@code daemon.properties}
032 * is used from the current directory.
033 * </p>
034 * <p>
035 * The properties file can have property values expanded at runtime
036 * by using System properties or execution environment. The part
037 * of the property value between {@code ${} and {@code }}
038 * will be used as System property or environment key. If found then
039 * the entire {@code ${foo}} will be replaced by the value of
040 * either system property or environment variable named {@code foo}.
041 * </p>
042 * <p>
043 * If no variable is found the {@code ${foo}}  will be passed as is.
044 * In case of {@code $${foo}} this will be unescaped and resulting
045 * value will be {@code ${foo}}.
046 * </p>
047 *
048 */
049public final class DaemonConfiguration
050{
051    /**
052     * Default configuration file name.
053     */
054    protected final static String DEFAULT_CONFIG        = "daemon.properties";
055    /**
056     * Property prefix
057     */
058    protected final static String PREFIX                = "daemon.";
059    private   final static String BTOKEN                = "${";
060    private   final static String ETOKEN                = "}";
061
062
063    private final Properties configurationProperties;
064    private final Properties systemProperties;
065
066    /**
067     * An empty immutable {@code String} array.
068     */
069    static final String[] EMPTY_STRING_ARRAY = {};
070
071    /**
072     * Default constructor
073     */
074    public DaemonConfiguration()
075    {
076        configurationProperties = new Properties();
077        systemProperties        = System.getProperties();
078    }
079
080    /**
081     * Loads the configuration properties file.
082     *
083     * @param fileName The properties file to load.
084     * @return {@code true} if the file was loaded.
085     */
086    public boolean load(String fileName)
087    {
088        if (fileName == null) {
089            fileName = DEFAULT_CONFIG;
090        }
091        
092        try (InputStream inputStream = new FileInputStream(fileName)) {
093            configurationProperties.clear();
094            configurationProperties.load(inputStream);
095            return true;
096        } catch (final IOException ex) {
097            // Error reading properties file
098            return false;
099        }
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     *
153     * @throws ParseException if the property is wrongly formatted.
154     *
155     * @return  Configuration property including any expansion/replacement
156     */
157    public String getProperty(final String name)
158        throws ParseException
159    {
160        if (name == null) {
161            return null;
162        }
163        return expandProperty(configurationProperties.getProperty(PREFIX + name));
164    }
165
166    /**
167     * Gets the configuration property array.
168     * <p>
169     * Property array is constructed form the list of properties
170     * which end with {@code [index]}
171     * </p>
172     * <pre>
173     * daemon.arg[0] = argument 1
174     * daemon.arg[1] = argument 2
175     * daemon.arg[2] = argument 3
176     * </pre>
177     * @param name The name of the property array to get.
178     *
179     * @throws ParseException if the property is wrongly formatted.
180     *
181     * @return  Configuration property array including any expansion/replacement
182     */
183    public String[] getPropertyArray(final String name)
184        throws ParseException
185    {
186        final ArrayList<String> list = new ArrayList<>();
187        String    args;
188
189        // Load daemon.arg[0] ... daemon.arg[n] into the String array.
190        //
191        while ((args = getProperty(name + "[" + list.size() + "]")) != null) {
192            list.add(args);
193        }
194        return list.toArray(EMPTY_STRING_ARRAY);
195    }
196}
197