View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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 static org.eclipse.jetty.start.UsageException.ERR_BAD_ARG;
22  
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Properties;
30  import java.util.Stack;
31  import java.util.TreeMap;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import org.eclipse.jetty.start.Props.Prop;
36  
37  /**
38   * Management of Properties.
39   * <p>
40   * 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,
41   * and also allowing for <code>${property}</code> expansion.
42   */
43  public final class Props implements Iterable<Prop>
44  {
45      private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}");
46      
47      public static class Prop
48      {
49          public String key;
50          public String value;
51          public String origin;
52          public Prop overrides;
53  
54          public Prop(String key, String value, String origin)
55          {
56              this.key = key;
57              this.value = value;
58              this.origin = origin;
59          }
60  
61          public Prop(String key, String value, String origin, Prop overrides)
62          {
63              this(key,value,origin);
64              this.overrides = overrides;
65          }
66  
67          @Override
68          public String toString()
69          {
70              StringBuilder builder = new StringBuilder();
71              builder.append("Prop [key=");
72              builder.append(key);
73              builder.append(", value=");
74              builder.append(value);
75              builder.append(", origin=");
76              builder.append(origin);
77              builder.append(", overrides=");
78              builder.append(overrides);
79              builder.append("]");
80              return builder.toString();
81          }
82      }
83  
84      public static final String ORIGIN_SYSPROP = "<system-property>";
85      
86      public static String getValue(String arg)
87      {
88          int idx = arg.indexOf('=');
89          if (idx == (-1))
90          {
91              throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
92          }
93          String value = arg.substring(idx + 1).trim();
94          if (value.length() <= 0)
95          {
96              throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg);
97          }
98          return value;
99      }
100 
101     public static List<String> getValues(String arg)
102     {
103         String v = getValue(arg);
104         ArrayList<String> l = new ArrayList<>();
105         for (String s : v.split(","))
106         {
107             if (s != null)
108             {
109                 s = s.trim();
110                 if (s.length() > 0)
111                 {
112                     l.add(s);
113                 }
114             }
115         }
116         return l;
117     }
118 
119     private Map<String, Prop> props = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
120     private List<String> sysPropTracking = new ArrayList<>();
121 
122     public void addAll(Props other)
123     {
124         this.props.putAll(other.props);
125         this.sysPropTracking.addAll(other.sysPropTracking);
126     }
127     
128     /**
129      * Add a potential argument as a property.
130      * <p>
131      * If arg is not a property, ignore it.
132      * @param arg the argument to parse for a potential property
133      * @param source the source for this argument (to track origin of property from)
134      * @return true if the property was added, false if the property wasn't added
135      */
136     public boolean addPossibleProperty(String arg, String source)
137     {
138         // Start property (syntax similar to System property)
139         if (arg.startsWith("-D"))
140         {
141             String[] assign = arg.substring(2).split("=",2);
142             switch (assign.length)
143             {
144                 case 2:
145                     setSystemProperty(assign[0],assign[1]);
146                     setProperty(assign[0],assign[1],source);
147                     return true;
148                 case 1:
149                     setSystemProperty(assign[0],"");
150                     setProperty(assign[0],"",source);
151                     return true;
152                 default:
153                     return false;
154             }
155         }
156 
157         // Is this a raw property declaration?
158         int idx = arg.indexOf('=');
159         if (idx >= 0)
160         {
161             String key = arg.substring(0,idx);
162             String value = arg.substring(idx + 1);
163 
164             setProperty(key,value,source);
165             return true;
166         }
167 
168         // All other strings are ignored
169         return false;
170     }
171 
172     public String cleanReference(String property)
173     {
174         String name = property.trim();
175         if (name.startsWith("${") && name.endsWith("}"))
176         {
177             name = name.substring(2,name.length() - 1);
178         }
179         return name.trim();
180     }
181 
182     public boolean containsKey(String key)
183     {
184         return props.containsKey(key);
185     }
186 
187     public String expand(String str)
188     {
189         return expand(str,new Stack<String>());
190     }
191 
192     public String expand(String str, Stack<String> seenStack)
193     {
194         if (str == null)
195         {
196             return str;
197         }
198 
199         if (str.indexOf("${") < 0)
200         {
201             // Contains no potential expressions.
202             return str;
203         }
204 
205         Matcher mat = __propertyPattern.matcher(str);
206         StringBuilder expanded = new StringBuilder();
207         int offset = 0;
208         String property;
209         String value;
210 
211         while (mat.find(offset))
212         {
213             property = mat.group(1);
214 
215             // Loop detection
216             if (seenStack.contains(property))
217             {
218                 StringBuilder err = new StringBuilder();
219                 err.append("Property expansion loop detected: ");
220                 int idx = seenStack.lastIndexOf(property);
221                 for (int i = idx; i < seenStack.size(); i++)
222                 {
223                     err.append(seenStack.get(i));
224                     err.append(" -> ");
225                 }
226                 err.append(property);
227                 throw new PropsException(err.toString());
228             }
229 
230             seenStack.push(property);
231 
232             // find property name
233             expanded.append(str.subSequence(offset,mat.start()));
234             // get property value
235             value = getString(property);
236             if (value == null)
237             {
238                 StartLog.trace("Unable to expand: %s",property);
239                 expanded.append(mat.group());
240             }
241             else
242             {
243                 // recursively expand
244                 value = expand(value,seenStack);
245                 expanded.append(value);
246             }
247             // update offset
248             offset = mat.end();
249         }
250 
251         // leftover
252         expanded.append(str.substring(offset));
253 
254         // special case for "$$"
255         if (expanded.indexOf("$$") >= 0)
256         {
257             return expanded.toString().replaceAll("\\$\\$","\\$");
258         }
259 
260         return expanded.toString();
261     }
262 
263     public Prop getProp(String key)
264     {
265         return getProp(key,true);
266     }
267 
268     public Prop getProp(String key, boolean searchSystemProps)
269     {
270         Prop prop = props.get(key);
271         if ((prop == null) && searchSystemProps)
272         {
273             // try system property
274             prop = getSystemProperty(key);
275         }
276         return prop;
277     }
278 
279     public String getString(String key)
280     {
281         if (key == null)
282         {
283             throw new PropsException("Cannot get value for null key");
284         }
285 
286         String name = cleanReference(key);
287 
288         if (name.length() == 0)
289         {
290             throw new PropsException("Cannot get value for empty key");
291         }
292 
293         Prop prop = getProp(name);
294         if (prop == null)
295         {
296             return null;
297         }
298         return prop.value;
299     }
300 
301     public String getString(String key, String defVal)
302     {
303         String val = getString(key);
304         if (val == null)
305         {
306             return defVal;
307         }
308         return val;
309     }
310 
311     private Prop getSystemProperty(String key)
312     {
313         String value = System.getProperty(key);
314         if (value == null)
315         {
316             return null;
317         }
318         return new Prop(key,value,ORIGIN_SYSPROP);
319     }
320 
321     public static boolean hasPropertyKey(String name)
322     {
323         return __propertyPattern.matcher(name).find();
324     }
325 
326     @Override
327     public Iterator<Prop> iterator()
328     {
329         return props.values().iterator();
330     }
331 
332     public void reset()
333     {
334         props.clear();
335     }
336 
337     public void setProperty(Prop prop)
338     {
339         props.put(prop.key,prop);
340     }
341 
342     public void setProperty(String key, String value, String origin)
343     {
344         Prop prop = props.get(key);
345         if (prop == null)
346         {
347             prop = new Prop(key,value,origin);
348         }
349         else
350         {
351             prop = new Prop(key,value,origin,prop);
352         }
353         props.put(key,prop);
354     }
355 
356     public int size()
357     {
358         return props.size();
359     }
360 
361     public void store(OutputStream stream, String comments) throws IOException
362     {
363         Properties props = new Properties();
364         // add all Props as normal properties, with expansion performed.
365         for (Prop prop : this)
366         {
367             props.setProperty(prop.key,expand(prop.value));
368         }
369         // write normal properties file
370         props.store(stream,comments);
371     }
372 
373     public void setSystemProperty(String key, String value)
374     {
375         System.setProperty(key,value);
376         sysPropTracking.add(key);
377     }
378 }