View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.start;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.Properties;
27  import java.util.Stack;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import org.eclipse.jetty.start.Props.Prop;
32  
33  /**
34   * Management of Properties.
35   * <p>
36   * This is larger in scope than the standard {@link java.util.Properties}, as it will also handle tracking the origin of each property, if it was overridden,
37   * and also allowing for <code>${property}</code> expansion.
38   */
39  public final class Props implements Iterable<Prop>
40  {
41      public static class Prop
42      {
43          public String key;
44          public String value;
45          public String origin;
46          public Prop overrides;
47  
48          public Prop(String key, String value, String origin)
49          {
50              this.key = key;
51              this.value = value;
52              this.origin = origin;
53          }
54  
55          public Prop(String key, String value, String origin, Prop overrides)
56          {
57              this(key,value,origin);
58              this.overrides = overrides;
59          }
60      }
61  
62      public static final String ORIGIN_SYSPROP = "<system-property>";
63  
64      private Map<String, Prop> props = new HashMap<>();
65  
66      public String cleanReference(String property)
67      {
68          String name = property.trim();
69          if (name.startsWith("${") && name.endsWith("}"))
70          {
71              name = name.substring(2,name.length() - 1);
72          }
73          return name.trim();
74      }
75  
76      public boolean containsKey(String key)
77      {
78          return props.containsKey(key);
79      }
80  
81      public String expand(String str)
82      {
83          return expand(str,new Stack<String>());
84      }
85  
86      public String expand(String str, Stack<String> seenStack)
87      {
88          if (str == null)
89          {
90              return str;
91          }
92  
93          if (str.indexOf("${") < 0)
94          {
95              // Contains no potential expressions.
96              return str;
97          }
98  
99          if (props.isEmpty())
100         {
101             // This is a stupid programming error, we should have something, even system properties
102             throw new PropsException("Props is empty: no properties declared!?");
103         }
104 
105         Pattern pat = Pattern.compile("(?<=[^$]|^)(\\$\\{[^}]*\\})");
106         Matcher mat = pat.matcher(str);
107         StringBuilder expanded = new StringBuilder();
108         int offset = 0;
109         String property;
110         String value;
111 
112         while (mat.find(offset))
113         {
114             property = cleanReference(mat.group(1));
115 
116             // Loop detection
117             if (seenStack.contains(property))
118             {
119                 StringBuilder err = new StringBuilder();
120                 err.append("Property expansion loop detected: ");
121                 int idx = seenStack.lastIndexOf(property);
122                 for (int i = idx; i < seenStack.size(); i++)
123                 {
124                     err.append(seenStack.get(i));
125                     err.append(" -> ");
126                 }
127                 err.append(property);
128                 throw new PropsException(err.toString());
129             }
130 
131             seenStack.push(property);
132 
133             // find property name
134             expanded.append(str.subSequence(offset,mat.start(1)));
135             // get property value
136             value = getString(property);
137             if (value == null)
138             {
139                 StartLog.debug("Unable to expand: %s",property);
140                 expanded.append(property);
141             }
142             else
143             {
144                 // recursively expand
145                 value = expand(value,seenStack);
146                 expanded.append(value);
147             }
148             // update offset
149             offset = mat.end(1);
150         }
151 
152         // leftover
153         expanded.append(str.substring(offset));
154 
155         // special case for "$$"
156         if (expanded.indexOf("$$") >= 0)
157         {
158             return expanded.toString().replaceAll("\\$\\$","\\$");
159         }
160 
161         return expanded.toString();
162     }
163 
164     public Prop getProp(String key)
165     {
166         Prop prop = props.get(key);
167         if (prop == null)
168         {
169             // try system property
170             prop = getSystemProperty(key);
171         }
172         return prop;
173     }
174 
175     public String getString(String key)
176     {
177         if (key == null)
178         {
179             throw new PropsException("Cannot get value for null key");
180         }
181 
182         String name = cleanReference(key);
183 
184         if (name.length() == 0)
185         {
186             throw new PropsException("Cannot get value for empty key");
187         }
188 
189         Prop prop = getProp(name);
190         if (prop == null)
191         {
192             return null;
193         }
194         return prop.value;
195     }
196 
197     public String getString(String key, String defVal)
198     {
199         String val = getString(key);
200         if (val == null)
201         {
202             return defVal;
203         }
204         return val;
205     }
206 
207     private Prop getSystemProperty(String key)
208     {
209         String value = System.getProperty(key);
210         if (value == null)
211         {
212             return null;
213         }
214         return new Prop(key,value,ORIGIN_SYSPROP);
215     }
216 
217     @Override
218     public Iterator<Prop> iterator()
219     {
220         return props.values().iterator();
221     }
222 
223     public void setProperty(Prop prop)
224     {
225         props.put(prop.key,prop);
226     }
227 
228     public void setProperty(String key, String value, String origin)
229     {
230         Prop prop = props.get(key);
231         if (prop == null)
232         {
233             prop = new Prop(key,value,origin);
234         }
235         else
236         {
237             prop = new Prop(key,value,origin,prop);
238         }
239         props.put(key,prop);
240     }
241 
242     public int size()
243     {
244         return props.size();
245     }
246 
247     public void store(OutputStream stream, String comments) throws IOException
248     {
249         Properties props = new Properties();
250         // add all Props as normal properties, with expansion performed.
251         for (Prop prop : this)
252         {
253             props.setProperty(prop.key,expand(prop.value));
254         }
255         // write normal properties file
256         props.store(stream,comments);
257     }
258 }