1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 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/**/*-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 }