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.util.ArrayList;
023import java.util.Properties;
024import java.text.ParseException;
025
026/**
027 * Used by jsvc for Daemon configuration.
028 * <p>
029 * Configuration is read from properties file.
030 * If no properties file is given the {@code daemon.properties}
031 * is used from the current directory.
032 * </p>
033 * <p>
034 * The properties file can have property values expanded at runtime
035 * by using System properties or execution environment. The part
036 * of the property value between {@code ${} and {@code }}
037 * will be used as System property or environment key. If found then
038 * the entire {@code ${foo}} will be replaced by the value of
039 * either system property or environment variable named {@code foo}.
040 * </p>
041 * <p>
042 * If no variable is found the {@code ${foo}}  will be passed as is.
043 * In case of {@code $${foo}} this will be unescaped and resulting
044 * value will be {@code ${foo}}.
045 * </p>
046 *
047 */
048public final class DaemonConfiguration
049{
050    /**
051     * Default configuration file name.
052     */
053    protected final static String DEFAULT_CONFIG        = "daemon.properties";
054    /**
055     * Property prefix
056     */
057    protected final static String PREFIX                = "daemon.";
058    private   final static String BTOKEN                = "${";
059    private   final static String ETOKEN                = "}";
060
061
062    private final Properties configurationProperties;
063    private final Properties systemProperties;
064
065    /**
066     * An empty immutable {@code String} array.
067     */
068    static final String[] EMPTY_STRING_ARRAY = {};
069
070    /**
071     * Default constructor
072     */
073    public DaemonConfiguration()
074    {
075        configurationProperties = new Properties();
076        systemProperties        = System.getProperties();
077    }
078
079    /**
080     * Loads the configuration properties file.
081     *
082     * @param fileName The properties file to load.
083     * @return {@code true} if the file was loaded.
084     */
085    public boolean load(String fileName)
086    {
087        boolean ok = false;
088        FileInputStream file = null;
089        try {
090            if (fileName == null) {
091                fileName = DEFAULT_CONFIG;
092            }
093            file = new FileInputStream(fileName);
094            configurationProperties.clear();
095            configurationProperties.load(file);
096            ok = true;
097        }
098        catch (final IOException ex) {
099            // Error reading properties file
100        } finally {
101            try {
102                if (file != null) {
103                    file.close();
104                }
105            } catch (final IOException ex) {
106            }
107        }
108        return ok;
109    }
110
111    private String expandProperty(final String propValue)
112        throws ParseException
113    {
114        final StringBuilder expanded;
115        int btoken;
116        int ctoken = 0;
117
118        if (propValue == null) {
119            return null;
120        }
121        expanded = new StringBuilder();
122        btoken   = propValue.indexOf(BTOKEN);
123        while (btoken != -1) {
124            if (btoken > 0 && propValue.charAt(btoken - 1) == BTOKEN.charAt(0)) {
125                // Skip and unquote.
126                expanded.append(propValue.substring(ctoken, btoken));
127                ctoken = btoken + 1;
128                btoken = propValue.indexOf(BTOKEN, btoken + BTOKEN.length());
129                continue;
130            }
131            final int etoken = propValue.indexOf(ETOKEN, btoken);
132            if (etoken == -1) {
133                // We have "${" without "}"
134                throw new ParseException("Error while looking for teminating '" +
135                                         ETOKEN + "'", btoken);
136            }
137            final String variable = propValue.substring(btoken + BTOKEN.length(), etoken);
138            String sysvalue = systemProperties.getProperty(variable);
139            if (sysvalue == null) {
140                // Try with the environment if there was no
141                // property by that name.
142                sysvalue = System.getenv(variable);
143            }
144            if (sysvalue != null) {
145                final String strtoken = propValue.substring(ctoken, btoken);
146                expanded.append(strtoken);
147                expanded.append(sysvalue);
148                ctoken = etoken + ETOKEN.length();
149            }
150            btoken = propValue.indexOf(BTOKEN, etoken + ETOKEN.length());
151        }
152        // Add what's left.
153        expanded.append(propValue.substring(ctoken));
154        return expanded.toString();
155    }
156
157    /**
158     * Gets the configuration property.
159     *
160     * @param name The name of the property to get.
161     *
162     * @throws ParseException if the property is wrongly formatted.
163     *
164     * @return  Configuration property including any expansion/replacement
165     */
166    public String getProperty(final String name)
167        throws ParseException
168    {
169        if (name == null) {
170            return null;
171        }
172        return expandProperty(configurationProperties.getProperty(PREFIX + name));
173    }
174
175    /**
176     * Gets the configuration property array.
177     * <p>
178     * Property array is constructed form the list of properties
179     * which end with {@code [index]}
180     * </p>
181     * <pre>
182     * daemon.arg[0] = argument 1
183     * daemon.arg[1] = argument 2
184     * daemon.arg[2] = argument 3
185     * </pre>
186     * @param name The name of the property array to get.
187     *
188     * @throws ParseException if the property is wrongly formatted.
189     *
190     * @return  Configuration property array including any expansion/replacement
191     */
192    public String[] getPropertyArray(final String name)
193        throws ParseException
194    {
195        final ArrayList<String> list = new ArrayList<>();
196        String    args;
197
198        // Load daemon.arg[0] ... daemon.arg[n] into the String array.
199        //
200        while ((args = getProperty(name + "[" + list.size() + "]")) != null) {
201            list.add(args);
202        }
203        return list.toArray(EMPTY_STRING_ARRAY);
204    }
205}
206