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 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 
134         sources = new ConfigSources();
135         sources.add(cmdLineSource);
136         this.homeDir = cmdLineSource.getHomePath();
137         this.baseDir = cmdLineSource.getBasePath();
138 
139         // TODO this is cyclic construction as start log uses BaseHome, but BaseHome constructor
140         // calls other constructors that log.   This appears to be a workable sequence.
141         StartLog.getInstance().initialize(this,cmdLineSource);
142         
143         sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath()));
144         sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath()));
145 
146         System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString());
147         System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString());
148     }
149 
150     public BaseHome(ConfigSources sources)
151     {
152         this.sources = sources;
153         Path home = null;
154         Path base = null;
155         for (ConfigSource source : sources)
156         {
157             if (source instanceof CommandLineConfigSource)
158             {
159                 CommandLineConfigSource cmdline = (CommandLineConfigSource)source;
160                 home = cmdline.getHomePath();
161                 base = cmdline.getBasePath();
162             }
163             else if (source instanceof JettyBaseConfigSource)
164             {
165                 base = ((JettyBaseConfigSource)source).getDir();
166             }
167             else if (source instanceof JettyHomeConfigSource)
168             {
169                 home = ((JettyHomeConfigSource)source).getDir();
170             }
171         }
172 
173         Objects.requireNonNull(home,"jetty.home cannot be null");
174         this.homeDir = home;
175         this.baseDir = (base != null)?base:home;
176 
177         System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString());
178         System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString());
179     }
180 
181     public String getBase()
182     {
183         if (baseDir == null)
184         {
185             return null;
186         }
187         return baseDir.toString();
188     }
189 
190     public Path getBasePath()
191     {
192         return baseDir;
193     }
194 
195     /**
196      * Create a {@link Path} reference to some content in <code>"${jetty.base}"</code>
197      * 
198      * @param path
199      *            the path to reference
200      * @return the file reference
201      */
202     public Path getBasePath(String path)
203     {
204         return baseDir.resolve(path);
205     }
206 
207     public ConfigSources getConfigSources()
208     {
209         return this.sources;
210     }
211 
212     public String getHome()
213     {
214         return homeDir.toString();
215     }
216 
217     public Path getHomePath()
218     {
219         return homeDir;
220     }
221 
222     /**
223      * Get a specific path reference.
224      * <p>
225      * Path references are searched based on the config source search order.
226      * <ol>
227      * <li>If provided path is an absolute reference., and exists, return that reference</li>
228      * <li>If exists relative to <code>${jetty.base}</code>, return that reference</li>
229      * <li>If exists relative to and <code>include-jetty-dir</code> locations, return that reference</li>
230      * <li>If exists relative to <code>${jetty.home}</code>, return that reference</li>
231      * <li>Return standard {@link Path} reference obtained from {@link java.nio.file.FileSystem#getPath(String, String...)} (no exists check performed)</li>
232      * </ol>
233      * 
234      * @param path
235      *            the path to get.
236      * @return the path reference.
237      */
238     public Path getPath(final String path)
239     {
240         Path apath = FS.toPath(path);
241 
242         if (apath.isAbsolute())
243         {
244             if (FS.exists(apath))
245             {
246                 return apath;
247             }
248         }
249 
250         for (ConfigSource source : sources)
251         {
252             if (source instanceof DirConfigSource)
253             {
254                 DirConfigSource dirsource = (DirConfigSource)source;
255                 Path file = dirsource.getDir().resolve(apath);
256                 if (FS.exists(file))
257                 {
258                     return file;
259                 }
260             }
261         }
262 
263         // Finally, as an anonymous path
264         return FS.toPath(path);
265     }
266 
267     /**
268      * Search specified Path with pattern and return hits
269      * 
270      * @param dir
271      *            the path to a directory to start search from
272      * @param searchDepth
273      *            the number of directories deep to perform the search
274      * @param pattern
275      *            the raw pattern to use for the search (must be relative)
276      * @return the list of Paths found
277      * @throws IOException
278      *             if unable to search the path
279      */
280     public List<Path> getPaths(Path dir, int searchDepth, String pattern) throws IOException
281     {
282         if (PathMatchers.isAbsolute(pattern))
283         {
284             throw new RuntimeException("Pattern cannot be absolute: " + pattern);
285         }
286 
287         List<Path> hits = new ArrayList<>();
288         if (FS.isValidDirectory(dir))
289         {
290             PathMatcher matcher = PathMatchers.getMatcher(pattern);
291             PathFinder finder = new PathFinder();
292             finder.setFileMatcher(matcher);
293             finder.setBase(dir);
294             finder.setIncludeDirsInResults(true);
295             Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder);
296             hits.addAll(finder.getHits());
297             Collections.sort(hits,new NaturalSort.Paths());
298         }
299         return hits;
300     }
301 
302     /**
303      * Get a List of {@link Path}s from a provided pattern.
304      * <p>
305      * Resolution Steps:
306      * <ol>
307      * <li>If the pattern starts with "regex:" or "glob:" then a standard {@link PathMatcher} is built using
308      * {@link java.nio.file.FileSystem#getPathMatcher(String)} as a file search.</li>
309      * <li>If pattern starts with a known filesystem root (using information from {@link java.nio.file.FileSystem#getRootDirectories()}) then this is assumed to
310      * be a absolute file system pattern.</li>
311      * <li>All other patterns are treated as relative to BaseHome information:
312      * <ol>
313      * <li>Search ${jetty.home} first</li>
314      * <li>Search ${jetty.base} for overrides</li>
315      * </ol>
316      * </li>
317      * </ol>
318      * <p>
319      * Pattern examples:
320      * <dl>
321      * <dt><code>lib/logging/*.jar</code></dt>
322      * <dd>Relative pattern, not recursive, search <code>${jetty.home}</code> then <code>${jetty.base}</code> for lib/logging/*.jar content</dd>
323      * 
324      * <dt><code>lib/**&#47;*-dev.jar</code></dt>
325      * <dd>Relative pattern, recursive search <code>${jetty.home}</code> then <code>${jetty.base}</code> for files under <code>lib</code> ending in
326      * <code>-dev.jar</code></dd>
327      * </dl>
328      * 
329      * <dt><code>etc/jetty.xml</code></dt>
330      * <dd>Relative pattern, no glob, search for <code>${jetty.home}/etc/jetty.xml</code> then <code>${jetty.base}/etc/jetty.xml</code></dd>
331      * 
332      * <dt><code>glob:/opt/app/common/*-corp.jar</code></dt>
333      * <dd>PathMapper pattern, glob, search <code>/opt/app/common/</code> for <code>*-corp.jar</code></code></dd>
334      * 
335      * </dl>
336      * 
337      * <p>
338      * Notes:
339      * <ul>
340      * <li>FileSystem case sensitivity is implementation specific (eg: linux is case-sensitive, windows is case-insensitive).<br/>
341      * See {@link java.nio.file.FileSystem#getPathMatcher(String)} for more details</li>
342      * <li>Pattern slashes are implementation neutral (use '/' always and you'll be fine)</li>
343      * <li>Recursive searching is limited to 30 levels deep (not configurable)</li>
344      * <li>File System loops are detected and skipped</li>
345      * </ul>
346      * 
347      * @param pattern
348      *            the pattern to search.
349      * @return the collection of paths found
350      * @throws IOException
351      *             if error during search operation
352      */
353     public List<Path> getPaths(String pattern) throws IOException
354     {
355         StartLog.debug("getPaths('%s')",pattern);
356         List<Path> hits = new ArrayList<>();
357 
358         if (PathMatchers.isAbsolute(pattern))
359         {
360             // Perform absolute path pattern search
361 
362             // The root to start search from
363             Path root = PathMatchers.getSearchRoot(pattern);
364             // The matcher for file hits
365             PathMatcher matcher = PathMatchers.getMatcher(pattern);
366 
367             if (FS.isValidDirectory(root))
368             {
369                 PathFinder finder = new PathFinder();
370                 finder.setIncludeDirsInResults(true);
371                 finder.setFileMatcher(matcher);
372                 finder.setBase(root);
373                 Files.walkFileTree(root,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder);
374                 hits.addAll(finder.getHits());
375             }
376         }
377         else
378         {
379             // Perform relative path pattern search
380             Path relativePath = PathMatchers.getSearchRoot(pattern);
381             PathMatcher matcher = PathMatchers.getMatcher(pattern);
382             PathFinder finder = new PathFinder();
383             finder.setIncludeDirsInResults(true);
384             finder.setFileMatcher(matcher);
385 
386             // walk config sources backwards ...
387             ListIterator<ConfigSource> iter = sources.reverseListIterator();
388             while (iter.hasPrevious())
389             {
390                 ConfigSource source = iter.previous();
391                 if (source instanceof DirConfigSource)
392                 {
393                     DirConfigSource dirsource = (DirConfigSource)source;
394                     Path dir = dirsource.getDir();
395                     Path deepDir = dir.resolve(relativePath);
396                     if (FS.isValidDirectory(deepDir))
397                     {
398                         finder.setBase(dir);
399                         Files.walkFileTree(deepDir,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder);
400                     }
401                 }
402             }
403 
404             hits.addAll(finder.getHits());
405         }
406 
407         Collections.sort(hits,new NaturalSort.Paths());
408         return hits;
409     }
410 
411     public boolean isBaseDifferent()
412     {
413         return homeDir.compareTo(baseDir) != 0;
414     }
415 
416     /**
417      * Convenience method for <code>toShortForm(file.toPath())</code>
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 }