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             // nothing to expand
102             // this situation can occur from --add-to-startd on a new blank base directory
103             return str;
104         }
105 
106         Pattern pat = Pattern.compile("(?<=[^$]|^)(\\$\\{[^}]*\\})");
107         Matcher mat = pat.matcher(str);
108         StringBuilder expanded = new StringBuilder();
109         int offset = 0;
110         String property;
111         String value;
112 
113         while (mat.find(offset))
114         {
115             property = cleanReference(mat.group(1));
116 
117             // Loop detection
118             if (seenStack.contains(property))
119             {
120                 StringBuilder err = new StringBuilder();
121                 err.append("Property expansion loop detected: ");
122                 int idx = seenStack.lastIndexOf(property);
123                 for (int i = idx; i < seenStack.size(); i++)
124                 {
125                     err.append(seenStack.get(i));
126                     err.append(" -> ");
127                 }
128                 err.append(property);
129                 throw new PropsException(err.toString());
130             }
131 
132             seenStack.push(property);
133 
134             // find property name
135             expanded.append(str.subSequence(offset,mat.start(1)));
136             // get property value
137             value = getString(property);
138             if (value == null)
139             {
140                 StartLog.debug("Unable to expand: %s",property);
141                 expanded.append(property);
142             }
143             else
144             {
145                 // recursively expand
146                 value = expand(value,seenStack);
147                 expanded.append(value);
148             }
149             // update offset
150             offset = mat.end(1);
151         }
152 
153         // leftover
154         expanded.append(str.substring(offset));
155 
156         // special case for "$$"
157         if (expanded.indexOf("$$") >= 0)
158         {
159             return expanded.toString().replaceAll("\\$\\$","\\$");
160         }
161 
162         return expanded.toString();
163     }
164 
165     public Prop getProp(String key)
166     {
167         Prop prop = props.get(key);
168         if (prop == null)
169         {
170             // try system property
171             prop = getSystemProperty(key);
172         }
173         return prop;
174     }
175 
176     public String getString(String key)
177     {
178         if (key == null)
179         {
180             throw new PropsException("Cannot get value for null key");
181         }
182 
183         String name = cleanReference(key);
184 
185         if (name.length() == 0)
186         {
187             throw new PropsException("Cannot get value for empty key");
188         }
189 
190         Prop prop = getProp(name);
191         if (prop == null)
192         {
193             return null;
194         }
195         return prop.value;
196     }
197 
198     public String getString(String key, String defVal)
199     {
200         String val = getString(key);
201         if (val == null)
202         {
203             return defVal;
204         }
205         return val;
206     }
207 
208     private Prop getSystemProperty(String key)
209     {
210         String value = System.getProperty(key);
211         if (value == null)
212         {
213             return null;
214         }
215         return new Prop(key,value,ORIGIN_SYSPROP);
216     }
217 
218     @Override
219     public Iterator<Prop> iterator()
220     {
221         return props.values().iterator();
222     }
223 
224     public void setProperty(Prop prop)
225     {
226         props.put(prop.key,prop);
227     }
228 
229     public void setProperty(String key, String value, String origin)
230     {
231         Prop prop = props.get(key);
232         if (prop == null)
233         {
234             prop = new Prop(key,value,origin);
235         }
236         else
237         {
238             prop = new Prop(key,value,origin,prop);
239         }
240         props.put(key,prop);
241     }
242 
243     public int size()
244     {
245         return props.size();
246     }
247 
248     public void store(OutputStream stream, String comments) throws IOException
249     {
250         Properties props = new Properties();
251         // add all Props as normal properties, with expansion performed.
252         for (Prop prop : this)
253         {
254             props.setProperty(prop.key,expand(prop.value));
255         }
256         // write normal properties file
257         props.store(stream,comments);
258     }
259 }