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.IOException;
22  import java.net.URI;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.eclipse.jetty.start.builders.StartDirBuilder;
29  import org.eclipse.jetty.start.builders.StartIniBuilder;
30  import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer;
31  import org.eclipse.jetty.start.fileinits.TestFileInitializer;
32  import org.eclipse.jetty.start.fileinits.UriFileInitializer;
33  import org.eclipse.jetty.start.graph.CriteriaSetPredicate;
34  import org.eclipse.jetty.start.graph.UniqueCriteriaPredicate;
35  import org.eclipse.jetty.start.graph.Predicate;
36  import org.eclipse.jetty.start.graph.Selection;
37  
38  /**
39   * Build a start configuration in <code>${jetty.base}</code>, including
40   * ini files, directories, and libs. Also handles License management.
41   */
42  public class BaseBuilder
43  {
44      public static interface Config
45      {
46          /**
47           * Add a module to the start environment in <code>${jetty.base}</code>
48           *
49           * @param module
50           *            the module to add
51           * @return true if module was added, false if module was not added
52           *         (because that module already exists)
53           * @throws IOException if unable to add the module
54           */
55          public boolean addModule(Module module) throws IOException;
56      }
57  
58      private static final String EXITING_LICENSE_NOT_ACKNOWLEDGED = "Exiting: license not acknowledged!";
59  
60      private final BaseHome baseHome;
61      private final List<FileInitializer> fileInitializers;
62      private final StartArgs startArgs;
63  
64      public BaseBuilder(BaseHome baseHome, StartArgs args)
65      {
66          this.baseHome = baseHome;
67          this.startArgs = args;
68          this.fileInitializers = new ArrayList<>();
69  
70          // Establish FileInitializers
71          if (args.isTestingModeEnabled())
72          {
73              // No downloads performed
74              fileInitializers.add(new TestFileInitializer());
75          }
76          else if (args.isDownload())
77          {
78              // Downloads are allowed to be performed
79              // Setup Maven Local Repo
80              Path localRepoDir = args.getMavenLocalRepoDir();
81              if (localRepoDir != null)
82              {
83                  // Use provided local repo directory
84                  fileInitializers.add(new MavenLocalRepoFileInitializer(baseHome,localRepoDir));
85              }
86              else
87              {
88                  // No no local repo directory (direct downloads)
89                  fileInitializers.add(new MavenLocalRepoFileInitializer(baseHome));
90              }
91  
92              // Normal URL downloads
93              fileInitializers.add(new UriFileInitializer(baseHome));
94          }
95      }
96  
97      private void ackLicenses() throws IOException
98      {
99          if (startArgs.isLicenseCheckRequired())
100         {
101             if (startArgs.isApproveAllLicenses())
102             {
103                 StartLog.info("All Licenses Approved via Command Line Option");
104             }
105             else
106             {
107                 Licensing licensing = new Licensing();
108                 for (Module module : startArgs.getAllModules().getSelected())
109                 {
110                     if (!module.hasFiles(baseHome,startArgs.getProperties()))
111                     {
112                         licensing.addModule(module);
113                     }
114                 }
115 
116                 if (licensing.hasLicenses())
117                 {
118                     StartLog.debug("Requesting License Acknowledgement");
119                     if (!licensing.acknowledgeLicenses())
120                     {
121                         StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
122                         System.exit(1);
123                     }
124                 }
125             }
126         }
127     }
128 
129     /**
130      * Build out the Base directory (if needed)
131      * 
132      * @return true if base directory was changed, false if left unchanged.
133      * @throws IOException if unable to build
134      */
135     public boolean build() throws IOException
136     {
137         Modules modules = startArgs.getAllModules();
138         boolean dirty = false;
139 
140         String dirCriteria = "<add-to-startd>";
141         String iniCriteria = "<add-to-start-ini>";
142         Selection startDirSelection = new Selection(dirCriteria);
143         Selection startIniSelection = new Selection(iniCriteria);
144         
145         List<String> startDNames = new ArrayList<>();
146         startDNames.addAll(startArgs.getAddToStartdIni());
147         List<String> startIniNames = new ArrayList<>();
148         startIniNames.addAll(startArgs.getAddToStartIni());
149 
150         int count = 0;
151         count += modules.selectNodes(startDNames,startDirSelection);
152         count += modules.selectNodes(startIniNames,startIniSelection);
153 
154         // look for ambiguous declaration found in both places
155         Predicate ambiguousPredicate = new CriteriaSetPredicate(dirCriteria,iniCriteria);
156         List<Module> ambiguous = modules.getMatching(ambiguousPredicate);
157 
158         if (ambiguous.size() > 0)
159         {
160             StringBuilder warn = new StringBuilder();
161             warn.append("Ambiguous module locations detected, defaulting to --add-to-start for the following module selections:");
162             warn.append(" [");
163             
164             for (int i = 0; i < ambiguous.size(); i++)
165             {
166                 if (i > 0)
167                 {
168                     warn.append(", ");
169                 }
170                 warn.append(ambiguous.get(i).getName());
171             }
172             warn.append(']');
173             StartLog.warn(warn.toString());
174         }
175 
176         StartLog.debug("Adding %s new module(s)",count);
177         
178         // Acknowledge Licenses
179         ackLicenses();
180 
181         // Collect specific modules to enable
182         // Should match 'criteria', with no other selections.explicit
183         Predicate startDMatcher = new UniqueCriteriaPredicate(dirCriteria);
184         Predicate startIniMatcher = new UniqueCriteriaPredicate(iniCriteria);
185 
186         List<Module> startDModules = modules.getMatching(startDMatcher);
187         List<Module> startIniModules = modules.getMatching(startIniMatcher);
188 
189         List<FileArg> files = new ArrayList<FileArg>();
190 
191         if (!startDModules.isEmpty())
192         {
193             StartDirBuilder builder = new StartDirBuilder(this);
194             for (Module mod : startDModules)
195             {
196                 if (ambiguous.contains(mod))
197                 {
198                     // skip ambiguous module
199                     continue;
200                 }
201                 
202                 if (mod.isSkipFilesValidation())
203                 {
204                     StartLog.debug("Skipping [files] validation on %s",mod.getName());
205                 } 
206                 else 
207                 {
208                     dirty |= builder.addModule(mod);
209                     for (String file : mod.getFiles())
210                     {
211                         files.add(new FileArg(mod,startArgs.getProperties().expand(file)));
212                     }
213                 }
214             }
215         }
216 
217         if (!startIniModules.isEmpty())
218         {
219             StartIniBuilder builder = new StartIniBuilder(this);
220             for (Module mod : startIniModules)
221             {
222                 if (mod.isSkipFilesValidation())
223                 {
224                     StartLog.debug("Skipping [files] validation on %s",mod.getName());
225                 } 
226                 else 
227                 {
228                     dirty |= builder.addModule(mod);
229                     for (String file : mod.getFiles())
230                     {
231                         files.add(new FileArg(mod,startArgs.getProperties().expand(file)));
232                     }
233                 }
234             }
235         }
236         
237         // Process files
238         files.addAll(startArgs.getFiles());
239         dirty |= processFileResources(files);
240 
241         return dirty;
242     }
243 
244     public BaseHome getBaseHome()
245     {
246         return baseHome;
247     }
248 
249     public StartArgs getStartArgs()
250     {
251         return startArgs;
252     }
253 
254     /**
255      * Process a specific file resource
256      * 
257      * @param arg
258      *            the fileArg to work with
259      * @param file
260      *            the resolved file reference to work with
261      * @return true if change was made as a result of the file, false if no change made.
262      * @throws IOException
263      *             if there was an issue in processing this file
264      */
265     private boolean processFileResource(FileArg arg, Path file) throws IOException
266     {
267         if (startArgs.isDownload() && (arg.uri != null))
268         {
269             // now on copy/download paths (be safe above all else)
270             if (!file.startsWith(baseHome.getBasePath()))
271             {
272                 throw new IOException("For security reasons, Jetty start is unable to process maven file resource not in ${jetty.base} - " + file);
273             }
274             
275             // make the directories in ${jetty.base} that we need
276             FS.ensureDirectoryExists(file.getParent());
277             
278             URI uri = URI.create(arg.uri);
279 
280             // Process via initializers
281             for (FileInitializer finit : fileInitializers)
282             {
283                 if (finit.init(uri,file,arg.location))
284                 {
285                     // Completed successfully
286                     return true;
287                 }
288             }
289 
290             return false;
291         }
292         else
293         {
294             // Process directly
295             boolean isDir = arg.location.endsWith("/");
296 
297             if (FS.exists(file))
298             {
299                 // Validate existence
300                 if (isDir)
301                 {
302                     if (!Files.isDirectory(file))
303                     {
304                         throw new IOException("Invalid: path should be a directory (but isn't): " + file);
305                     }
306                     if (!FS.canReadDirectory(file))
307                     {
308                         throw new IOException("Unable to read directory: " + file);
309                     }
310                 }
311                 else
312                 {
313                     if (!FS.canReadFile(file))
314                     {
315                         throw new IOException("Unable to read file: " + file);
316                     }
317                 }
318 
319                 return false;
320             }
321 
322             if (isDir)
323             {
324                 // Create directory
325                 StartLog.log("MKDIR",baseHome.toShortForm(file));
326                 return FS.ensureDirectoryExists(file);
327             }
328             else
329             {
330                 // Warn on missing file (this has to be resolved manually by user)
331                 String shortRef = baseHome.toShortForm(file);
332                 if (startArgs.isTestingModeEnabled())
333                 {
334                     StartLog.log("TESTING MODE","Skipping required file check on: %s",shortRef);
335                     return true;
336                 }
337 
338                 StartLog.warn("Missing Required File: %s",baseHome.toShortForm(file));
339                 startArgs.setRun(false);
340                 if (arg.uri != null)
341                 {
342                     StartLog.warn("  Can be downloaded From: %s",arg.uri);
343                     StartLog.warn("  Run start.jar --create-files to download");
344                 }
345 
346                 return true;
347             }
348         }
349     }
350 
351     /**
352      * Process the {@link FileArg} for startup, assume that all licenses have
353      * been acknowledged at this stage.
354      *
355      * @param files
356      *            the list of {@link FileArg}s to process
357      * @return true if base directory modified, false if left untouched
358      */
359     private boolean processFileResources(List<FileArg> files) throws IOException
360     {
361         if ((files == null) || (files.isEmpty()))
362         {
363             return false;
364         }
365 
366         boolean dirty = false;
367 
368         List<String> failures = new ArrayList<String>();
369 
370         for (FileArg arg : files)
371         {
372             Path file = baseHome.getBasePath(arg.location);
373             try
374             {
375                 dirty |= processFileResource(arg,file);
376             }
377             catch (Throwable t)
378             {
379                 StartLog.warn(t);
380                 failures.add(String.format("[%s] %s - %s",t.getClass().getSimpleName(),t.getMessage(),file.toAbsolutePath().toString()));
381             }
382         }
383 
384         if (!failures.isEmpty())
385         {
386             StringBuilder err = new StringBuilder();
387             err.append("Failed to process all file resources.");
388             for (String failure : failures)
389             {
390                 err.append(System.lineSeparator()).append(" - ").append(failure);
391             }
392             StartLog.warn(err.toString());
393 
394             throw new RuntimeException(err.toString());
395         }
396 
397         return dirty;
398     }
399 }