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.quickstart;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.URI;
24  import java.net.URL;
25  import java.nio.file.FileSystems;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.Comparator;
31  import java.util.List;
32  import java.util.Stack;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  import org.eclipse.jetty.util.URIUtil;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.util.resource.Resource;
40  
41  /**
42   * Normalize Attribute to String.
43   * <p>
44   * Replaces and expands:
45   * <ul>
46   * <li>${WAR}</li>
47   * <li>${jetty.base}</li>
48   * <li>${jetty.home}</li>
49   * <li>${user.home}</li>
50   * <li>${user.dir}</li>
51   * </ul>
52   */
53  public class AttributeNormalizer
54  {
55      private static final Logger LOG = Log.getLogger(AttributeNormalizer.class);
56      private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}");
57  
58      private static class PathAttribute
59      {
60          public final Path path;
61          public final String key;
62          private int weight = -1;
63  
64          public PathAttribute(String key, Path path) throws IOException
65          {
66              this.key = key;
67              this.path = toCanonicalPath(path);
68              // TODO: Don't allow non-directory paths? (but what if the path doesn't exist?)
69          }
70  
71          public PathAttribute(String key, String systemPropertyKey) throws IOException
72          {
73              this(key, toCanonicalPath(System.getProperty(systemPropertyKey)));
74          }
75          
76          private static Path toCanonicalPath(String path) throws IOException
77          {
78              if (path == null)
79              {
80                  return null;
81              }
82              return toCanonicalPath(FileSystems.getDefault().getPath(path));
83          }
84          
85          private static Path toCanonicalPath(Path path) throws IOException
86          {
87              if (path == null)
88              {
89                  return null;
90              }
91              if (Files.exists(path))
92              {
93                  return path.toRealPath();
94              }
95              return path.toAbsolutePath();
96          }
97  
98          public PathAttribute weight(int newweight)
99          {
100             this.weight = newweight;
101             return this;
102         }
103         
104         @Override
105         public String toString()
106         {
107             return String.format("PathAttribute[%s=>%s,%d]",key,path,weight);
108         }
109     }
110 
111     private static class PathAttributeComparator implements Comparator<PathAttribute>
112     {
113         @Override
114         public int compare(PathAttribute o1, PathAttribute o2)
115         {
116             if( (o1.path == null) && (o2.path != null) )
117             {
118                 return -1;
119             }
120             
121             if( (o1.path != null) && (o2.path == null) )
122             {
123                 return 1;
124             }
125             
126             if( (o1.path == null) && (o2.path == null) )
127             {
128                 return 0;
129             }
130             
131             // Different lengths?
132             int diff = o2.path.getNameCount() - o1.path.getNameCount();
133             if(diff != 0)
134             {
135                 return diff;
136             }
137             
138             // Different names?
139             diff = o2.path.compareTo(o1.path);
140             if(diff != 0)
141             {
142                 return diff;
143             }
144             
145             // The paths are the same, base now on weight
146             return o2.weight - o1.weight;
147         }
148     }
149 
150     public static String uriSeparators(String path)
151     {
152         StringBuilder ret = new StringBuilder();
153         for (char c : path.toCharArray())
154         {
155             if ((c == '/') || (c == '\\'))
156             {
157                 ret.append('/');
158             }
159             else
160             {
161                 ret.append(c);
162             }
163         }
164         return ret.toString();
165     }
166 
167     private URI warURI;
168     private List<PathAttribute> attributes = new ArrayList<>();
169 
170     public AttributeNormalizer(Resource baseResource)
171     {
172         // WAR URI is always evaluated before paths.
173         warURI = baseResource == null ? null : baseResource.getURI();
174         // We don't normalize or resolve the baseResource URI
175         if (!warURI.isAbsolute())
176             throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
177         try
178         {
179             // Track path attributes for expansion
180             attributes.add(new PathAttribute("jetty.base", "jetty.base").weight(9));
181             attributes.add(new PathAttribute("jetty.home", "jetty.home").weight(8));
182             attributes.add(new PathAttribute("user.home", "user.home").weight(7));
183             attributes.add(new PathAttribute("user.dir", "user.dir").weight(6));
184             
185             Collections.sort(attributes, new PathAttributeComparator());
186 
187             if (LOG.isDebugEnabled())
188             {
189                 int i = 0;
190                 for (PathAttribute attr : attributes)
191                 {
192                     LOG.debug(" [{}] {}", i++, attr);
193                 }
194             }
195         }
196         catch (Exception e)
197         {
198             throw new IllegalArgumentException(e);
199         }
200     }
201 
202     public String normalize(Object o)
203     {
204         try
205         {
206             // Find a URI
207             URI uri = null;
208             if (o instanceof URI)
209                 uri = (URI)o;
210             else if (o instanceof URL)
211                 uri = ((URL)o).toURI();
212             else if (o instanceof File)
213                 uri = ((File)o).toURI();
214             else
215             {
216                 String s = o.toString();
217                 uri = new URI(s);
218                 if (uri.getScheme() == null)
219                     return s;
220             }
221 
222             if ("jar".equalsIgnoreCase(uri.getScheme()))
223             {
224                 String raw = uri.getRawSchemeSpecificPart();
225                 int bang = raw.indexOf("!/");
226                 String normal = normalize(raw.substring(0,bang));
227                 String suffix = raw.substring(bang);
228                 return "jar:" + normal + suffix;
229             }
230             else if ("file".equalsIgnoreCase(uri.getScheme()))
231             {
232                 return "file:" + normalizePath(new File(uri.getRawSchemeSpecificPart()).toPath());
233             }
234             else
235             {
236                 if(uri.isAbsolute())
237                 {
238                     return normalizeUri(uri);
239                 }
240             }
241         }
242         catch (Exception e)
243         {
244             LOG.warn(e);
245         }
246         return String.valueOf(o);
247     }
248     
249     public String normalizeUri(URI uri)
250     {
251         String uriStr = uri.toASCIIString();
252         String warStr = warURI.toASCIIString();
253         if (uriStr.startsWith(warStr))
254         {
255             return "${WAR}" + uriStr.substring(warStr.length());
256         }
257         return uriStr;
258     }
259 
260     public String normalizePath(Path path)
261     {
262         for (PathAttribute attr : attributes)
263         {
264             if (attr.path == null)
265                 continue;
266             
267             try
268             {
269                 if (path.startsWith(attr.path) || path.equals(attr.path) || Files.isSameFile(path,attr.path))
270                 {
271                     return uriSeparators(URIUtil.addPaths("${" + attr.key + "}",attr.path.relativize(path).toString()));
272                 }
273             }
274             catch (IOException ignore)
275             {
276                 LOG.ignore(ignore);
277             }
278         }
279 
280         return uriSeparators(path.toString());
281     }
282 
283     public String expand(String str)
284     {
285         return expand(str,new Stack<String>());
286     }
287 
288     public String expand(String str, Stack<String> seenStack)
289     {
290         if (str == null)
291         {
292             return str;
293         }
294 
295         if (str.indexOf("${") < 0)
296         {
297             // Contains no potential expressions.
298             return str;
299         }
300 
301         Matcher mat = __propertyPattern.matcher(str);
302         StringBuilder expanded = new StringBuilder();
303         int offset = 0;
304         String property;
305         String value;
306 
307         while (mat.find(offset))
308         {
309             property = mat.group(1);
310 
311             // Loop detection
312             if (seenStack.contains(property))
313             {
314                 StringBuilder err = new StringBuilder();
315                 err.append("Property expansion loop detected: ");
316                 int idx = seenStack.lastIndexOf(property);
317                 for (int i = idx; i < seenStack.size(); i++)
318                 {
319                     err.append(seenStack.get(i));
320                     err.append(" -> ");
321                 }
322                 err.append(property);
323                 throw new RuntimeException(err.toString());
324             }
325 
326             seenStack.push(property);
327 
328             // find property name
329             expanded.append(str.subSequence(offset,mat.start()));
330             // get property value
331             value = getString(property);
332             if (value == null)
333             {
334                 if(LOG.isDebugEnabled())
335                     LOG.debug("Unable to expand: {}",property);
336                 expanded.append(mat.group());
337             }
338             else
339             {
340                 // recursively expand
341                 value = expand(value,seenStack);
342                 expanded.append(value);
343             }
344             // update offset
345             offset = mat.end();
346         }
347 
348         // leftover
349         expanded.append(str.substring(offset));
350 
351         // special case for "$$"
352         if (expanded.indexOf("$$") >= 0)
353         {
354             return expanded.toString().replaceAll("\\$\\$","\\$");
355         }
356 
357         return expanded.toString();
358     }
359 
360     private String getString(String property)
361     {
362         if(property == null)
363         {
364             return null;
365         }
366 
367         // Use war path (if known)
368         if("WAR".equalsIgnoreCase(property))
369         {
370             return warURI.toASCIIString();
371         }
372         
373         // Use known path attributes
374         for (PathAttribute attr : attributes)
375         {
376             if (attr.key.equalsIgnoreCase(property))
377             {
378                 return uriSeparators(attr.path.toString());
379             }
380         }
381 
382         // Use system properties next
383         return System.getProperty(property);
384     }
385 }