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