001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.microservice;
014
015import java.io.*;
016import java.nio.file.Paths;
017import java.util.*;
018import java.util.jar.*;
019import java.util.logging.*;
020
021import org.apache.juneau.ExecutableException;
022import org.apache.juneau.config.*;
023import org.apache.juneau.config.store.*;
024import org.apache.juneau.config.vars.*;
025import org.apache.juneau.microservice.console.*;
026import org.apache.juneau.svl.*;
027import org.apache.juneau.utils.*;
028
029/**
030 * Builder for {@link Microservice} class.
031 *
032 * <p>
033 * Instances of this class are created using {@link Microservice#create()}.
034 */
035public class MicroserviceBuilder {
036
037   Args args;
038   ManifestFile manifest;
039   Logger logger;
040   LogConfig logConfig;
041   Config config;
042   String configName;
043   ConfigStore configStore;
044   ConfigBuilder configBuilder = Config.create();
045   Boolean consoleEnabled;
046   List<ConsoleCommand> consoleCommands = new ArrayList<>();
047   VarResolverBuilder varResolverBuilder = VarResolver.create().defaultVars().vars(ConfigVar.class);
048   Scanner consoleReader;
049   PrintWriter consoleWriter;
050   MicroserviceListener listener;
051   File workingDir = System.getProperty("juneau.workingDir") == null ? null : new File(System.getProperty("juneau.workingDir"));
052
053   /**
054    * Constructor.
055    */
056   protected MicroserviceBuilder() {}
057
058   /**
059    * Copy constructor.
060    *
061    * @param copyFrom The builder to copy settings from.
062    */
063   protected MicroserviceBuilder(MicroserviceBuilder copyFrom) {
064      this.args = copyFrom.args;
065      this.manifest = copyFrom.manifest;
066      this.logger = copyFrom.logger;
067      this.configName = copyFrom.configName;
068      this.logConfig = copyFrom.logConfig == null ? null : copyFrom.logConfig.copy();
069      this.consoleEnabled = copyFrom.consoleEnabled;
070      this.configBuilder = copyFrom.configBuilder;
071      this.varResolverBuilder = copyFrom.varResolverBuilder;
072      this.consoleReader = copyFrom.consoleReader;
073      this.consoleWriter = copyFrom.consoleWriter;
074      this.workingDir = copyFrom.workingDir;
075   }
076
077   /**
078    * Creates a copy of this builder.
079    *
080    * @return A new copy of this builder.
081    */
082   public MicroserviceBuilder copy() {
083      return new MicroserviceBuilder(this);
084   }
085
086   /**
087    * Instantiate a new microservice using the settings defined on this builder.
088    *
089    * @return A new microservice.
090    * @throws Exception Error occurred.
091    */
092   public Microservice build() throws Exception {
093      return new Microservice(this);
094   }
095
096   /**
097    * Specifies the command-line arguments passed into the Java command.
098    *
099    * <p>
100    * This is required if you use {@link Microservice#getArgs()} or <c>$A</c> string variables.
101    *
102    * @param args
103    *    The command-line arguments passed into the Java command as a pre-parsed {@link Args} object.
104    * @return This object (for method chaining).
105    */
106   public MicroserviceBuilder args(Args args) {
107      this.args = args;
108      return this;
109   }
110
111   /**
112    * Specifies the command-line arguments passed into the Java command.
113    *
114    * <p>
115    * This is required if you use {@link Microservice#getArgs()} or <c>$A</c> string variables.
116    *
117    * @param args
118    *    The command-line arguments passed into the Java command as the raw command-line arguments.
119    * @return This object (for method chaining).
120    */
121   public MicroserviceBuilder args(String...args) {
122      this.args = new Args(args);
123      return this;
124   }
125
126   /**
127    * Specifies the manifest file of the jar file this microservice is contained within.
128    *
129    * <p>
130    * This is required if you use {@link Microservice#getManifest()}.
131    * It's also used to locate initialization values such as <c>Main-Config</c>.
132    *
133    * <p>
134    * If you do not specify the manifest file, we attempt to resolve it through the following methods:
135    * <ol class='spaced-list'>
136    *    <li>
137    *       Looking on the file system for a file at <js>"META-INF/MANIFEST.MF"</js>.
138    *       This is primarily to allow for running microservices from within eclipse workspaces where the manifest file
139    *       is located in the project root.
140    *    <li>
141    *       Using the class loader for this class to find the file at the URL <js>"META-INF/MANIFEST.MF"</js>.
142    * </ol>
143    *
144    * @param value
145    *    The manifest file of this microservice.
146    *    <br>Can be any of the following types:
147    *    <ul>
148    *       <li>{@link ManifestFile}
149    *       <li>{@link Manifest}
150    *       <li>{@link Reader} - Containing the raw contents of the manifest.  Note that the input must end with a newline.
151    *       <li>{@link InputStream} - Containing the raw contents of the manifest.  Note that the input must end with a newline.
152    *       <li>{@link File} - File containing the raw contents of the manifest.
153    *       <li>{@link String} - Path to file containing the raw contents of the manifest.
154    *       <li>{@link Class} - Finds and loads the manifest file of the jar file that the specified class is contained within.
155    *    </ul>
156    * @return This object (for method chaining).
157    * @throws IOException Thrown by underlying stream.
158    */
159   public MicroserviceBuilder manifest(Object value) throws IOException {
160      if (value == null)
161         this.manifest = null;
162      else if (value instanceof ManifestFile)
163         this.manifest = (ManifestFile)value;
164      else if (value instanceof Manifest)
165         this.manifest = new ManifestFile((Manifest)value);
166      else if (value instanceof Reader)
167         this.manifest = new ManifestFile((Reader)value);
168      else if (value instanceof InputStream)
169         this.manifest = new ManifestFile((InputStream)value);
170      else if (value instanceof File)
171         this.manifest = new ManifestFile((File)value);
172      else if (value instanceof String)
173         this.manifest = new ManifestFile(resolveFile((String)value));
174      else if (value instanceof Class)
175         this.manifest = new ManifestFile((Class<?>)value);
176      else
177         throw new RuntimeException("Invalid type passed to MicroserviceBuilder.manifest(Object).  Type=["+value.getClass().getName()+"]");
178
179      return this;
180   }
181
182   /**
183    * Specifies the logger used by the microservice and returned by the {@link Microservice#getLogger()} method.
184    *
185    * <p>
186    * Calling this method overrides the default logging mechanism controlled by the {@link #logConfig(LogConfig)} method.
187    *
188    * @param logger The logger to use for logging microservice messages.
189    * @return This object (for method chaining).
190    */
191   public MicroserviceBuilder logger(Logger logger) {
192      this.logger = logger;
193      return this;
194   }
195
196   /**
197    * Specifies logging instructions for the microservice.
198    *
199    * <p>
200    * If not specified, the values are taken from the <js>"Logging"</js> section of the configuration.
201    *
202    * <p>
203    * This method is ignored if {@link #logger(Logger)} is used to set the microservice logger.
204    *
205    * @param logConfig The log configuration.
206    * @return This object (for method chaining).
207    */
208   public MicroserviceBuilder logConfig(LogConfig logConfig) {
209      this.logConfig = logConfig;
210      return this;
211   }
212
213   /**
214    * Specifies the config for initializing this microservice.
215    *
216    * <p>
217    * Calling this method overrides the default configuration controlled by the {@link #configName(String)} and {@link #configStore(ConfigStore)} methods.
218    *
219    * @param config The configuration.
220    * @return This object (for method chaining).
221    */
222   public MicroserviceBuilder config(Config config) {
223      this.config = config;
224      return this;
225   }
226
227   /**
228    * Specifies the config name for initializing this microservice.
229    *
230    * <p>
231    * If you do not specify the config file location, we attempt to resolve it through the following methods:
232    * <ol class='spaced-list'>
233    *    <li>
234    *       Resolve file first in working directory, then in classpath, using the following names:
235    *       <ul>
236    *          <li>
237    *             The <js>"configFile"</js> argument in the command line arguments passed in through the constructor.
238    *          <li>
239    *             The value of the <c>Main-Config</c> entry in the manifest file.
240    *          <li>
241    *             A config file in the same location and with the same name as the executable jar file.
242    *             (e.g. <js>"java -jar myjar.jar"</js> will look for <js>"myjar.cfg"</js>).
243    *       </ul>
244    *    <li>
245    *       Resolve any <js>"*.cfg"</js> file that can be found in the working directory.
246    *    <li>
247    *       Resolve any of the following files in the classpath:  <js>"juneau.cfg"</js>, <js>"system.cfg"</js>
248    * </ol>
249    *
250    * <p>
251    * If no configuration file is found, and empty in-memory configuration is used.
252    *
253    * @param configName The configuration name.
254    * @return This object (for method chaining).
255    */
256   public MicroserviceBuilder configName(String configName) {
257      this.configName = configName;
258      return this;
259   }
260
261   /**
262    * Specifies the config store to use for storing and retrieving configurations.
263    *
264    * <p>
265    * By default, we use a {@link ConfigFileStore} store for configuration files.
266    *
267    * @param configStore The configuration name.
268    * @return This object (for method chaining).
269    */
270   public MicroserviceBuilder configStore(ConfigStore configStore) {
271      this.configStore = configStore;
272      return this;
273   }
274
275   /**
276    * Specifies that the Java console is enabled for this microservice.
277    *
278    * <p>
279    * If not specified, this value is taken from the <js>"Console/enabled"</js> configuration setting.
280    * If not specified in the configuration, defaults to <jk>false</jk>.
281    *
282    * @param consoleEnabled <jk>true</jk> if the Java console is enabled for this microservice.
283    * @return This object (for method chaining).
284    */
285   public MicroserviceBuilder consoleEnabled(boolean consoleEnabled) {
286      this.consoleEnabled = consoleEnabled;
287      return this;
288   }
289
290   /**
291    * Specifies console commands to make available on the Java console.
292    *
293    * <p>
294    * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
295    *
296    * <p>
297    * This list augments the commands defined via the <js>"Console/commands"</js> configuration setting.
298    *
299    * <p>
300    * This method can only be used on console commands with no-arg constructors.
301    *
302    * @param consoleCommands The list of console commands to append to the list of available commands.
303    * @return This object (for method chaining).
304    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
305    */
306   @SuppressWarnings("unchecked")
307   public MicroserviceBuilder consoleCommands(Class<? extends ConsoleCommand>...consoleCommands) throws ExecutableException {
308      try {
309         for (Class<? extends ConsoleCommand> cc : consoleCommands)
310            this.consoleCommands.add(cc.newInstance());
311      } catch (InstantiationException | IllegalAccessException e) {
312         throw new ExecutableException(e);
313      }
314      return this;
315   }
316
317   /**
318    * Specifies console commands to make available on the Java console.
319    *
320    * <p>
321    * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
322    *
323    * <p>
324    * This list augments the commands defined via the <js>"Console/commands"</js> configuration setting.
325    *
326    * @param consoleCommands The list of console commands to append to the list of available commands.
327    * @return This object (for method chaining).
328    */
329   public MicroserviceBuilder consoleCommands(ConsoleCommand...consoleCommands) {
330      this.consoleCommands.addAll(Arrays.asList(consoleCommands));
331      return this;
332   }
333
334   /**
335    * Specifies the console input and output.
336    *
337    * <p>
338    * If not specified, uses the console returned by {@link System#console()}.
339    * If that is not available, uses {@link System#in} and {@link System#out}.
340    *
341    * <p>
342    * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
343    *
344    * @param consoleReader The console input.
345    * @param consoleWriter The console output.
346    * @return This object (for method chaining).
347    */
348   public MicroserviceBuilder console(Scanner consoleReader, PrintWriter consoleWriter) {
349      this.consoleReader = consoleReader;
350      this.consoleWriter = consoleWriter;
351      return this;
352   }
353
354   /**
355    * Augments the set of variables defined in the configuration and var resolver.
356    *
357    * <p>
358    * This calls {@link VarResolverBuilder#vars(Class[])} on the var resolver used to construct the configuration
359    * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}.
360    *
361    * @param vars The set of variables to append to the var resolver builder.
362    * @return This object (for method chaining).
363    */
364   @SuppressWarnings("unchecked")
365   public MicroserviceBuilder vars(Class<? extends Var>...vars) {
366      varResolverBuilder.vars(vars);
367      return this;
368   }
369
370   /**
371    * Adds a var resolver context object for vars defined in the configuration and var resolver.
372    *
373    * <p>
374    * This calls {@link VarResolverBuilder#contextObject(String,Object)} on the var resolver used to construct the configuration
375    * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}.
376    *
377    * @param name The context object name.
378    * @param object The context object.
379    * @return This object (for method chaining).
380    */
381   public MicroserviceBuilder varContext(String name, Object object) {
382      varResolverBuilder.contextObject(name, object);
383      return this;
384   }
385
386   /**
387    * Specifies the directory to use to resolve the config file and other paths defined with the config file.
388    *
389    * @param workingDir The working directory, or <jk>null</jk> to use the underlying working directory.
390    * @return This object (for method chaining).
391    */
392   public MicroserviceBuilder workingDir(File workingDir) {
393      this.workingDir = workingDir;
394      return this;
395   }
396
397   /**
398    * Specifies the directory to use to resolve the config file and other paths defined with the config file.
399    *
400    * @param workingDir The working directory, or <jk>null</jk> to use the underlying working directory.
401    * @return This object (for method chaining).
402    */
403   public MicroserviceBuilder workingDir(String workingDir) {
404      this.workingDir = new File(workingDir);
405      return this;
406   }
407
408   /**
409    * Registers an event listener for this microservice.
410    *
411    * @param listener An event listener for this microservice.
412    * @return This object (for method chaining).
413    */
414   public MicroserviceBuilder listener(MicroserviceListener listener) {
415      this.listener = listener;
416      return this;
417   }
418
419   /**
420    * Resolves the specified path.
421    *
422    * <p>
423    * If the working directory has been explicitly specified, relative paths are resolved relative to that.
424    *
425    * @param path The path to resolve.
426    * @return The resolved file.
427    */
428   protected File resolveFile(String path) {
429      if (Paths.get(path).isAbsolute())
430         return new File(path);
431      if (workingDir != null)
432         return new File(workingDir, path);
433      return new File(path);
434   }
435}