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 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 if (fileName == null) { 088 fileName = DEFAULT_CONFIG; 089 } 090 091 try (InputStream inputStream = new FileInputStream(fileName)) { 092 configurationProperties.clear(); 093 configurationProperties.load(inputStream); 094 return true; 095 } catch (final IOException ex) { 096 // Error reading properties file 097 return false; 098 } 099 } 100 101 private String expandProperty(final String propValue) 102 throws ParseException 103 { 104 final StringBuilder expanded; 105 int btoken; 106 int ctoken = 0; 107 108 if (propValue == null) { 109 return null; 110 } 111 expanded = new StringBuilder(); 112 btoken = propValue.indexOf(BTOKEN); 113 while (btoken != -1) { 114 if (btoken > 0 && propValue.charAt(btoken - 1) == BTOKEN.charAt(0)) { 115 // Skip and unquote. 116 expanded.append(propValue.substring(ctoken, btoken)); 117 ctoken = btoken + 1; 118 btoken = propValue.indexOf(BTOKEN, btoken + BTOKEN.length()); 119 continue; 120 } 121 final int etoken = propValue.indexOf(ETOKEN, btoken); 122 if (etoken == -1) { 123 // We have "${" without "}" 124 throw new ParseException("Error while looking for teminating '" + 125 ETOKEN + "'", btoken); 126 } 127 final String variable = propValue.substring(btoken + BTOKEN.length(), etoken); 128 String sysvalue = systemProperties.getProperty(variable); 129 if (sysvalue == null) { 130 // Try with the environment if there was no 131 // property by that name. 132 sysvalue = System.getenv(variable); 133 } 134 if (sysvalue != null) { 135 final String strtoken = propValue.substring(ctoken, btoken); 136 expanded.append(strtoken); 137 expanded.append(sysvalue); 138 ctoken = etoken + ETOKEN.length(); 139 } 140 btoken = propValue.indexOf(BTOKEN, etoken + ETOKEN.length()); 141 } 142 // Add what's left. 143 expanded.append(propValue.substring(ctoken)); 144 return expanded.toString(); 145 } 146 147 /** 148 * Gets the configuration property. 149 * 150 * @param name The name of the property to get. 151 * 152 * @throws ParseException if the property is wrongly formatted. 153 * 154 * @return Configuration property including any expansion/replacement 155 */ 156 public String getProperty(final String name) 157 throws ParseException 158 { 159 if (name == null) { 160 return null; 161 } 162 return expandProperty(configurationProperties.getProperty(PREFIX + name)); 163 } 164 165 /** 166 * Gets the configuration property array. 167 * <p> 168 * Property array is constructed form the list of properties 169 * which end with {@code [index]} 170 * </p> 171 * <pre> 172 * daemon.arg[0] = argument 1 173 * daemon.arg[1] = argument 2 174 * daemon.arg[2] = argument 3 175 * </pre> 176 * @param name The name of the property array to get. 177 * 178 * @throws ParseException if the property is wrongly formatted. 179 * 180 * @return Configuration property array including any expansion/replacement 181 */ 182 public String[] getPropertyArray(final String name) 183 throws ParseException 184 { 185 final ArrayList<String> list = new ArrayList<>(); 186 String args; 187 188 // Load daemon.arg[0] ... daemon.arg[n] into the String array. 189 // 190 while ((args = getProperty(name + "[" + list.size() + "]")) != null) { 191 list.add(args); 192 } 193 return list.toArray(EMPTY_STRING_ARRAY); 194 } 195} 196