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