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 java.io.File;
22  import java.io.IOException;
23  import java.nio.file.FileVisitOption;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.PathMatcher;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.EnumSet;
30  import java.util.List;
31  import java.util.ListIterator;
32  import java.util.Objects;
33  
34  import org.eclipse.jetty.start.config.CommandLineConfigSource;
35  import org.eclipse.jetty.start.config.ConfigSource;
36  import org.eclipse.jetty.start.config.ConfigSources;
37  import org.eclipse.jetty.start.config.DirConfigSource;
38  import org.eclipse.jetty.start.config.JettyBaseConfigSource;
39  import org.eclipse.jetty.start.config.JettyHomeConfigSource;
40  
41  /**
42   * File access for <code>${jetty.home}</code>, <code>${jetty.base}</code>, directories.
43   * <p>
44   * By default, both <code>${jetty.home}</code> and <code>${jetty.base}</code> are the same directory, but they can point at different directories.
45   * <p>
46   * The <code>${jetty.home}</code> directory is where the main Jetty binaries and default configuration is housed.
47   * <p>
48   * The <code>${jetty.base}</code> directory is where the execution specific configuration and webapps are obtained from.
49   */
50  public class BaseHome
51  {
52      public static class SearchDir
53      {
54          private Path dir;
55          private String name;
56  
57          public SearchDir(String name)
58          {
59              this.name = name;
60          }
61  
62          public Path getDir()
63          {
64              return dir;
65          }
66  
67          public Path resolve(Path subpath)
68          {
69              return dir.resolve(subpath);
70          }
71  
72          public Path resolve(String subpath)
73          {
74              return dir.resolve(FS.separators(subpath));
75          }
76  
77          public SearchDir setDir(File path)
78          {
79              if (path != null)
80              {
81                  return setDir(path.toPath());
82              }
83              return this;
84          }
85  
86          public SearchDir setDir(Path path)
87          {
88              if (path != null)
89              {
90                  this.dir = path.toAbsolutePath();
91              }
92              return this;
93          }
94  
95          public SearchDir setDir(String path)
96          {
97              if (path != null)
98              {
99                  return setDir(FS.toPath(path));
100             }
101             return this;
102         }
103 
104         public String toShortForm(Path path)
105         {
106             Path relative = dir.relativize(path);
107             return String.format("${%s}%c%s",name,File.separatorChar,relative.toString());
108         }
109     }
110 
111     public static final String JETTY_BASE = "jetty.base";
112     public static final String JETTY_HOME = "jetty.home";
113     private final static EnumSet<FileVisitOption> SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
114 
115     private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10);
116 
117     private final ConfigSources sources;
118     private final Path homeDir;
119     private final Path baseDir;
120 
121     public BaseHome() throws IOException
122     {
123         this(new String[0]);
124     }
125 
126     public BaseHome(String cmdLine[]) throws IOException
127     {
128         this(new CommandLineConfigSource(cmdLine));
129     }
130 
131     public BaseHome(CommandLineConfigSource cmdLineSource) throws IOException
132     {
133         sources = new ConfigSources();
134         sources.add(cmdLineSource);
135         this.homeDir = cmdLineSource.getHomePath();
136         this.baseDir = cmdLineSource.getBasePath();
137 
138         // TODO this is cyclic construction as start log uses BaseHome, but BaseHome constructor
139         // calls other constructors that log.   This appears to be a workable sequence.
140         StartLog.getInstance().initialize(this,cmdLineSource);
141         
142         sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath()));
143         sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath()));
144 
145         System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString());
146         System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString());
147     }
148 
149     public BaseHome(ConfigSources sources)
150     {
151         this.sources = sources;
152         Path home = null;
153         Path base = null;
154         for (ConfigSource source : sources)
155         {
156             if (source instanceof CommandLineConfigSource)
157             {
158                 CommandLineConfigSource cmdline = (CommandLineConfigSource)source;
159                 home = cmdline.getHomePath();
160                 base = cmdline.getBasePath();
161             }
162             else if (source instanceof JettyBaseConfigSource)
163             {
164                 base = ((JettyBaseConfigSource)source).getDir();
165             }
166             else if (source instanceof JettyHomeConfigSource)
167             {
168                 home = ((JettyHomeConfigSource)source).getDir();
169             }
170         }
171 
172         Objects.requireNonNull(home,"jetty.home cannot be null");
173         this.homeDir = home;
174         this.baseDir = (base != null)?base:home;
175 
176         System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString());
177         System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString());
178     }
179 
180     public String getBase()
181     {
182         if (baseDir == null)
183         {
184             return null;
185         }
186         return baseDir.toString();
187     }
188 
189     public Path getBasePath()
190     {
191         return baseDir;
192     }
193 
194     /**
195      * Create a {@link Path} reference to some content in <code>"${jetty.base}"</code>
196      * 
197      * @param path
198      *            the path to reference
199      * @return the file reference
200      */
201     public Path getBasePath(String path)
202     {
203         return baseDir.resolve(path).normalize().toAbsolutePath();
204     }
205 
206     public ConfigSources getConfigSources()
207     {
208         return this.sources;
209     }
210 
211     public String getHome()
212     {
213         return homeDir.toString();
214     }
215 
216     public Path getHomePath()
217     {
218         return homeDir;
219     }
220 
221     /**
222      * Get a specific path reference.
223      * <p>
224      * Path references are searched based on the config source search order.
225      * <ol>
226      * <li>If provided path is an absolute reference., and exists, return that reference</li>
227      * <li>If exists relative to <code>${jetty.base}</code>, return that reference</li>
228      * <li>If exists relative to and <code>include-jetty-dir</code> locations, return that reference</li>
229      * <li>If exists relative to <code>${jetty.home}</code>, return that reference</li>
230      * <li>Return standard {@link Path} reference obtained from {@link java.nio.file.FileSystem#getPath(String, String...)} (no exists check performed)</li>
231      * </ol>
232      * 
233      * @param path
234      *            the path to get.
235      * @return the path reference.
236      */
237     public Path getPath(final String path)
238     {
239         Path apath = FS.toPath(path);
240 
241         if (apath.isAbsolute())
242         {
243             if (FS.exists(apath))
244             {
245                 return apath;
246             }
247         }
248 
249         for (ConfigSource source : sources)
250         {
251             if (source instanceof DirConfigSource)
252             {
253                 DirConfigSource dirsource = (DirConfigSource)source;
254                 Path file = dirsource.getDir().resolve(apath);
255                 if (FS.exists(file))
256                 {
257                     return file;
258                 }
259             }
260         }
261 
262         // Finally, as an anonymous path
263         return FS.toPath(path);
264     }
265 
266     /**
267      * Search specified Path with pattern and return hits
268      * 
269      * @param dir
270      *            the path to a directory to start search from
271      * @param searchDepth
272      *            the number of directories deep to perform the search
273      * @param pattern
274      *            the raw pattern to use for the search (must be relative)
275      * @return the list of Paths found
276      * @throws IOException
277      *             if unable to search the path
278      */
279     public List<Path> getPaths(Path dir, int searchDepth, String pattern) throws IOException
280     {
281         if (PathMatchers.isAbsolute(pattern))
282         {
283             throw new RuntimeException("Pattern cannot be absolute: " + pattern);
284         }
285 
286         List<Path> hits = new ArrayList<>();
287         if (FS.isValidDirectory(dir))
288         {
289             PathMatcher matcher = PathMatchers.getMatcher(pattern);
290             PathFinder finder = new PathFinder();
291             finder.setFileMatcher(matcher);
292             finder.setBase(dir);
293             finder.setIncludeDirsInResults(true);
294             Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder);
295             hits.addAll(finder.getHits());
296             Collections.sort(hits,new NaturalSort.Paths());
297         }
298         return hits;
299     }
300 
301     /**
302      * Get a List of {@link Path}s from a provided pattern.
303      * <p>
304      * Resolution Steps:
305      * <ol>
306      * <li>If the pattern starts with "regex:" or "glob:" then a standard {@link PathMatcher} is built using
307      * {@link java.nio.file.FileSystem#getPathMatcher(String)} as a file search.</li>
308      * <li>If pattern starts with a known filesystem root (using information from {@link java.nio.file.FileSystem#getRootDirectories()}) then this is assumed to
309      * be a absolute file system pattern.</li>
310      * <li>All other patterns are treated as relative to BaseHome information:
311      * <ol>
312      * <li>Search ${jetty.home} first</li>
313      * <li>Search ${jetty.base} for overrides</li>
314      * </ol>
315      * </li>
316      * </ol>
317      * <p>
318      * Pattern examples:
319      * <dl>
320      * <dt><code>lib/logging/*.jar</code></dt>
321      * <dd>Relative pattern, not recursive, search <code>${jetty.home}</code> then <code>${jetty.base}</code> for lib/logging/*.jar content</dd>
322      * 
323      * <dt><code>lib/**&#47;*-dev.jar</code></dt>
324      * <dd>Relative pattern, recursive search <code>${jetty.home}</code> then <code>${jetty.base}</code> for files under <code>lib</code> ending in
325      * <code>-dev.jar</code></dd>
326      * 
327      * <dt><code>etc/jetty.xml</code></dt>
328      * <dd>Relative pattern, no glob, search for <code>${jetty.home}/etc/jetty.xml</code> then <code>${jetty.base}/etc/jetty.xml</code></dd>
329      * 
330      * <dt><code>glob:/opt/app/common/*-corp.jar</code></dt>
331      * <dd>PathMapper pattern, glob, search <code>/opt/app/common/</code> for <code>*-corp.jar</code></dd>
332      * 
333      * </dl>
334      * 
335      * <p>
336      * Notes:
337      * <ul>
338      * <li>FileSystem case sensitivity is implementation specific (eg: linux is case-sensitive, windows is case-insensitive).<br>
339      * See {@link java.nio.file.FileSystem#getPathMatcher(String)} for more details</li>
340      * <li>Pattern slashes are implementation neutral (use '/' always and you'll be fine)</li>
341      * <li>Recursive searching is limited to 30 levels deep (not configurable)</li>
342      * <li>File System loops are detected and skipped</li>
343      * </ul>
344      * 
345      * @param pattern
346      *            the pattern to search.
347      * @return the collection of paths found
348      * @throws IOException
349      *             if error during search operation
350      */
351     public List<Path> getPaths(String pattern) throws IOException
352     {
353         StartLog.debug("getPaths('%s')",pattern);
354         List<Path> hits = new ArrayList<>();
355 
356         if (PathMatchers.isAbsolute(pattern))
357         {
358             // Perform absolute path pattern search
359 
360             // The root to start search from
361             Path root = PathMatchers.getSearchRoot(pattern);
362             // The matcher for file hits
363             PathMatcher matcher = PathMatchers.getMatcher(pattern);
364 
365             if (FS.isValidDirectory(root))
366             {
367                 PathFinder finder = new PathFinder();
368                 finder.setIncludeDirsInResults(true);
369                 finder.setFileMatcher(matcher);
370                 finder.setBase(root);
371                 Files.walkFileTree(root,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder);
372                 hits.addAll(finder.getHits());
373             }
374         }
375         else
376         {
377             // Perform relative path pattern search
378             Path relativePath = PathMatchers.getSearchRoot(pattern);
379             PathMatcher matcher = PathMatchers.getMatcher(pattern);
380             PathFinder finder = new PathFinder();
381             finder.setIncludeDirsInResults(true);
382             finder.setFileMatcher(matcher);
383 
384             // walk config sources backwards ...
385             ListIterator<ConfigSource> iter = sources.reverseListIterator();
386             while (iter.hasPrevious())
387             {
388                 ConfigSource source = iter.previous();
389                 if (source instanceof DirConfigSource)
390                 {
391                     DirConfigSource dirsource = (DirConfigSource)source;
392                     Path dir = dirsource.getDir();
393                     Path deepDir = dir.resolve(relativePath);
394                     if (FS.isValidDirectory(deepDir))
395                     {
396                         finder.setBase(dir);
397                         Files.walkFileTree(deepDir,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder);
398                     }
399                 }
400             }
401 
402             hits.addAll(finder.getHits());
403         }
404 
405         Collections.sort(hits,new NaturalSort.Paths());
406         return hits;
407     }
408 
409     public boolean isBaseDifferent()
410     {
411         return homeDir.compareTo(baseDir) != 0;
412     }
413 
414     /**
415      * Convenience method for <code>toShortForm(file.toPath())</code>
416      * @param path the path to shorten
417      * @return the short form of the path as a String
418      */
419     public String toShortForm(final File path)
420     {
421         return toShortForm(path.toPath());
422     }
423 
424     /**
425      * Replace/Shorten arbitrary path with property strings <code>"${jetty.home}"</code> or <code>"${jetty.base}"</code> where appropriate.
426      * 
427      * @param path
428      *            the path to shorten
429      * @return the potentially shortened path
430      */
431     public String toShortForm(final Path path)
432     {
433         Path apath = path.toAbsolutePath();
434 
435         for (ConfigSource source : sources)
436         {
437             if (source instanceof DirConfigSource)
438             {
439                 DirConfigSource dirsource = (DirConfigSource)source;
440                 Path dir = dirsource.getDir();
441                 if (apath.startsWith(dir))
442                 {
443                     if (dirsource.isPropertyBased())
444                     {
445                         Path relative = dir.relativize(apath);
446                         return String.format("%s%c%s",dirsource.getId(),File.separatorChar,relative.toString());
447                     }
448                     else
449                     {
450                         return apath.toString();
451                     }
452                 }
453             }
454         }
455 
456         return apath.toString();
457     }
458 
459     /**
460      * Replace/Shorten arbitrary path with property strings <code>"${jetty.home}"</code> or <code>"${jetty.base}"</code> where appropriate.
461      * 
462      * @param path
463      *            the path to shorten
464      * @return the potentially shortened path
465      */
466     public String toShortForm(final String path)
467     {
468         if ((path == null) || (path.charAt(0) == '<'))
469         {
470             return path;
471         }
472 
473         return toShortForm(FS.toPath(path));
474     }
475 }