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