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