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 */ 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 private final Properties configurationProperties; 062 private final Properties systemProperties; 063 064 /** 065 * An empty immutable {@code String} array. 066 */ 067 static final String[] EMPTY_STRING_ARRAY = {}; 068 069 /** 070 * Default constructor 071 */ 072 public DaemonConfiguration() 073 { 074 configurationProperties = new Properties(); 075 systemProperties = System.getProperties(); 076 } 077 078 /** 079 * Loads the configuration properties file. 080 * 081 * @param fileName The properties file to load. 082 * @return {@code true} if the file was loaded. 083 */ 084 public boolean load(String fileName) 085 { 086 if (fileName == null) { 087 fileName = DEFAULT_CONFIG; 088 } 089 090 try (InputStream inputStream = new FileInputStream(fileName)) { 091 configurationProperties.clear(); 092 configurationProperties.load(inputStream); 093 return true; 094 } catch (final IOException ex) { 095 // Error reading properties file 096 return false; 097 } 098 } 099 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 * 151 * @throws ParseException if the property is wrongly formatted. 152 * 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 * @param name The name of the property array to get. 176 * 177 * @throws ParseException if the property is wrongly formatted. 178 * 179 * @return Configuration property array including any expansion/replacement 180 */ 181 public String[] getPropertyArray(final String name) 182 throws ParseException 183 { 184 final ArrayList<String> list = new ArrayList<>(); 185 String args; 186 187 // Load daemon.arg[0] ... daemon.arg[n] into the String array. 188 // 189 while ((args = getProperty(name + "[" + list.size() + "]")) != null) { 190 list.add(args); 191 } 192 return list.toArray(EMPTY_STRING_ARRAY); 193 } 194} 195