Module org.update4j
Package org.update4j

Class Configuration

Object
Configuration

public class Configuration
extends Object
This class is the heart of the framework. It contains all the logic required to draft new releases at the development side, or start the application at the client side.

Although everything is contained in a single class; some methods are intended to run either on the dev machine -- to draft new releases -- or client machine, not both. The documentation of each method will point that out.

A configuration (or config) is linked to an XML file, and this class provide methods to read, write, generate and sync configurations. Once a configuration has been created, it is immutable and cannot be modified. There are methods to manipulate the XML elements and create new configurations from them, but the original remains untouched.

Terminology

  • Bootstrap Application — The JVM startup application that solely does the update and launch logic or anything at that level apart from the Business Application.
  • Business Application — The application you actually want to make updatable.
  • Boot Classpath/Modulepath — The JVM native classpath/modulepath where the bootstrap is usually loaded.
  • Dynamic Classpath/Modulepath — The dynamically loaded classpath/modulepath where the business application is loaded.
  • URI — The remote (or in case of file:/// -- local) location from where the file is downloaded.
  • Path — The local location to where the file should be downloaded on the client machine.
  • Service — Any of the service interfaces that can be provided by developers and easily updatable by releasing newer providers with a higher version number.
  • Property — Any of the key-value pairs listed in the config file. Can be used as placeholders in URI's, paths, class names, file comments or in values of other properties.

Developer Side

You should primarily use this class whenever you want to draft a new release to generate new configs. This might be done directly in code or be called by build tools.

There are 3 ways generate configs.

1. Using the Builder API

builder() is the entry point to the config builder API.

Here's a sample config created with this approach:

 Configuration config = Configuration.builder()
                 // resolve uri and path of each individual file against the base.
                 // if not present you must provider the absolute location to every individual file
                 // with the uri() and path() method
                 .baseUri("http://example.com/")
 
                 // reads actual value from client system property "user.home"
                 .basePath("${user.home}/myapp/")
 
                 // list all files from the given directory
                 .files(FileMetadata.streamDirectory("build/mylibs")
                                 // mark all jar files for classpath
                                 .peek(r -> r.classpath(r.getSource().toString().endsWith(".jar"))))
 
                 .file(FileMetadata.readFrom("otherDirectory/my-logo.png")
 
                                 //override http://example.com above
                                 .uri("https://s3.aws.com/some-location/img.png")
 
                                 // resolves base from basePath but
                                 // overrides my-logo.png from source
                                 .path("application-logo.png"))
 
                 // we're done!
                 .build();
 

2. Synchronizing Existing Configuration

If you already have a configuration but you changed files without adding or removing files, you might synchronize the file size, checksum and signature (if you use signature validation) without using the builder API, via sync(). A new config will be returned, the existing config remains untouched:

Given this XML config.xml:

 <configuration timestamp="2018-08-22T19:31:40.448450500Z">
     <base uri="https://example.com/" path="${user.loc}"/>
     <properties>
         <property key="user.loc" value="${user.home}/Desktop/"/>
     </properties>
     <files>
         <file path="file1.jar" size="1348" checksum="fd7adfb7"/>
     </files>
 </configuration>
 

You can synchronize it as:

 Configuration config = Configuration.read(Files.newBufferedReader(Paths.get("config.xml")));
 
 // read files from actual locations listed in config
 // in our case ${user.home}/Desktop/file1.jar
 Configuration newConfig = config.sync();
 
 // read files from different base path but same individual file name
 // in our case ./build/file1.jar where "." is current directory
 Configuration newConfig = config.sync(Paths.get("build"));
 
 // read files from actual locations listed in config
 // and sign with given PrivateKey
 KeyStore ks = KeyStore.getInstance("JKS");
 ks.load(Files.newInputStream(keystorePath), "Password1".toCharArray());
 
 PrivateKey pk = (PrivateKey) ks.getKey("alias", "Password2".toCharArray());
 Configuration newConfig = config.sync(pk);
 

If you want to add a new file you should manually add the filename in the config XML and sync() will do the rest:

 <files>
         <file path="file1.jar" size="1348" checksum="fd7adfb7"/>
         
         <!-- The new file -->
         <file path="file2.jar" />
 </files>
 

3. Manual XML Manipulation

You can access the XML DOM with the ConfigMapper class and load a config using parse(ConfigMapper) to obtain a new configuration. You can also write a mapper without parsing — essentially skipping all validations — by the ConfigMapper.write(Writer).

 ConfigMapper mapper = new ConfigMapper();
 mapper.baseUri = "https://example.com/"
 
 FileMapper file = new FileMapper();
 file.path = "/root/home/file.jar";
 file.size = 3082;
 file.checksum = "ac29bfa0";
 mapper.files.add(file);
 
 Configuration config = Configuration.parse(mapper);
 

Or access the underlying mapper of an existing config (it creates a defensive copy, so cache them once):

 ConfigMapper mapper = config.generateXmlMapper();
 mapper.files.remove(0);
 
 Configuration newConfig = Configuration.parse(mapper);
 

Writing the Configuration

Once you successfully created a config, write them with write(Writer):

 try (Writer out = Files.newBufferedWriter(location)) {
     config.write(out);
 }
 
 // or get a String
 String theXml = config.toString();
 system.out.println(theXml);
 

Client Side

A config in the client serves two purposes: 1. Know when a file is outdated and requires an update when update() is called 2. List of files to be dynamically loaded onto the running JVM when launch() is called.

All logic on the client side is generally done in the bootstrap application, like reading the config (and usually caching it somewhere locally in case there's no Internet connection next time), updating and launching or whatever needs to be done before launch or after business app shutdown or anywhere in between, according to your needs.

In order to use the config you must first read it from a file or remote location.

Reading a Configuration

Read a config using the read(Reader):

 Configuration config = null;
 
 try (InputStream in = new URL("https://example.com/config.xml").openStream()) {
     config = Configuration.read(new InputStreamReader(in));
 }
 

Updating

Updating is done by calling any of the update() methods. You can update without signature validation, or with, by using the PublicKey overloads.

You can update before launching so the subsequent launch always has the newest version, or you can update afterwards and only get the new version on next restart. In the latter case — on Windows — you cannot update existing files, since the JVM locks them upon launch; you can call any of the updateTemp() overloads and complete the update on next restart via Update.finalizeUpdate(Path).

When update is called without explicitly passing an UpdateHandler instance and getUpdateHandler() returns null, the framework will try to locate one between the registered service providers and will use the one with the highest version() number. If getUpdateHandler() returns a class name, it will load that class instead.

For more info how to register providers please refer to the Github Wiki.

Regular updating (not updateTemp()):

 // loads a registered provider or DefaultUpdateHandler if non are found.
 config.update(); // returns a boolean if succeeded
 
 // updates with given update handler
 config.update(new MyUpdateHandler());
 
 // update and inject fields to and from the handler
 config.update(myInjector);
 
 // update and validate against the public key
 config.update(myPubKey);
 

Or you can update to a temporary location and finalize on next restart. Here's a sample lifecycle:

 public static void main(String[] args) throws IOException {
     // the temporary location
     Path temp = Paths.get("update");
 
     // first check if last run made a temp update
     if (Update.containsUpdate(temp)) {
         Update.finalizeUpdate(temp);
     }
 
     // some random method
     Configuration config = getConfig();
     // we don't want to hang, so we can update immediately
     new Thread(() -> config.launch()).start();
 
     // and *after* launch do the update
     if (config.requiresUpdate()) {
         config.updateTemp(temp);
     }
 }
 

Consistency

If even a single file failed to download or if any other exception arises, all downloads before the exception is rolled back to its original state as before the call for update.

Boot Modulepath Conflicts

Every jar file gets checked if it were a valid file in boot modulepath; such as if it's a valid zip file, duplicate module name, split package, valid automatic module name etc., no matter if the file was actually intended to be present in the boot modulepath. This was put in place to prevent accidentally making the file visible to the boot modulepath and completely breaking the application, as the JVM would resist to startup, thus not allowing this to be fixed remotely.

If great care was taken that the given file will not be visible to the boot modulepath (i.e. only to the dynamic path or boot classpath), it is legal and you may override the check by marking ignoreBootConflict="true" in the config file or via the corresponding builder method.

Signature

Optionally, to secure your clients in the event of server compromise, you can sign the configuration and files via the signer() method in the Builder API, or sync(PrivateKey).

The config signature ensures that it has not been tampered, as changed URIs, paths or properties. Changes that are not significant to the framework, as whitespaces or element/attribute order do not matter. Changing element order in lists (i.e. properties or files) do change the ordering in the end-resulting list, and is part of the signature. The timestamp field is never part of the signature. To verify the config itself, read it with the read(Reader, PublicKey) overload, or invoke verifyConfiguration(PublicKey).

To verify files on update, use the PublicKey overload of update() or updateTemp() and it will reject the download if any file fails.

Launching

Launching loads files onto the dynamic classpath or modulepath (or does not load it if not marked with either), depending on their configuration and launches it by using either a passed Launcher, or by loading a launcher provider.

When launch is called without explicitly passing a Launcher instance and getLauncher() returns null, the framework will try to locate one between the registered service providers and will use the one with the highest version() number. If getLauncher() returns a class name, it will load that class instead.

If an explicit launcher instance was passed, it only has reflective access to the Business Application by reflecting against LaunchContext.getClassLoader().

 // launch with registered launcher or DefaultLauncher if non were found
 config.launch();
 
 // launch and inject fields to and from the launcher
 // assume the caller implements Injectable
 config.launch(this);
 
 // launch with passed launcher, *only reflective access*
 config.launch(new MyLauncher());
 
Author:
Mordechai Meisels
  • Nested Class Summary

    Nested Classes 
    Modifier and Type Class Description
    static class  Configuration.Builder
    This class is used to generate new configurations when a new draft is released.
  • Method Summary

    Modifier and Type Method Description
    static Configuration.Builder builder()
    The entry point to the Builder API.
    void deleteOldFiles​(Configuration oldConfig)
    Convenience method to delete files only present in oldConfig and clean up app directory.
    void deleteOldFiles​(Configuration oldConfig, boolean matchChecksum, int secondsDelay)
    Convenience method to delete files only present in oldConfig and clean up app directory.
    boolean equals​(Object other)
    Returns whether the given configuration is equals to this.
    ConfigMapper generateXmlMapper()
    Generates a new XML mapper for direct XML manipulation with values populated identical to this configuration.
    Path getBasePath()
    Returns the base path against whom all relative paths in individual files are resolved.
    URI getBaseUri()
    Returns the base URI against whom all relative URIs in individual files are resolved.
    List<FileMetadata> getFiles()
    Returns the list of files listed in the configuration file.
    String getLauncher()
    Returns the Launcher class name that should be used instead of of the default highest version currently present in the classpath or modulepath.
    List<FileMetadata> getOldFiles​(Configuration oldConfig, boolean matchChecksum)
    Returns a list of files of old files present in oldConfig but not in the current.
    List<Property> getProperties()
    Returns an unmodifiable list of properties listed in the configuration file.
    List<Property> getProperties​(String key)
    Returns a list of properties listed in the configuration file that have the provided key.
    Map<String,​String> getResolvedProperties()
    Returns an unmodifiable map of keys and values after resolving the placeholders.
    String getResolvedProperty​(String key)
    Returns the real value of the property with the given key, after resolving the placeholders.
    String getSignature()
    Returns the signature for this configuration.
    Instant getTimestamp()
    Returns the timestamp this configuration was last updated using the Configuration.Builder API or sync().
    String getUpdateHandler()
    Returns the UpdateHandler class name that should be used instead of of the default highest version currently present in the classpath or modulepath.
    String implyPlaceholders​(String str)
    Returns a string with real values replaced with placeholders.
    String implyPlaceholders​(String str, boolean isPath)
    Returns a string with real values replaced with placeholders.
    String implyPlaceholders​(String str, PlaceholderMatchType matchType)
    Returns a string with real values replaced with placeholders.
    String implyPlaceholders​(String str, PlaceholderMatchType matchType, boolean isPath)
    Returns a string with real values replaced with placeholders.
    void launch()
    Launches the business application by loading all files marked with the classpath or modulepath attributes, on their respective paths, dynamically.
    void launch​(Injectable injectable)
    Launches the business application by loading all files marked with the classpath or modulepath attributes, on their respective paths, dynamically.
    void launch​(Launcher launcher)
    Launches the business application by loading all files marked with the classpath or modulepath attributes, on their respective paths, dynamically.
    static Configuration parse​(ConfigMapper mapper)
    Parses a configuration from the given XML mapper.
    static Configuration parse​(ConfigMapper mapper, Map<String,​String> dynamicProperties)
    Parses a configuration from the given XML mapper, and add the provided properties.
    static Configuration read​(Reader reader)
    Reads and parses a configuration XML.
    static Configuration read​(Reader reader, PublicKey key)
    Reads and parses a configuration XML, then verifies the configuration signature against the public key.
    static Configuration read​(Reader reader, PublicKey key, Map<String,​String> dynamicProperties)
    Reads and parses a configuration XML and add more properties, then verifies the configuration signature against the public key.
    static Configuration read​(Reader reader, Map<String,​String> dynamicProperties)
    Reads and parses a configuration XML, and add the provided properties.
    boolean requiresUpdate()
    Checks the metadata of every file and returns true if at-least one file requires an update, and false if no file requires an update.
    String resolvePlaceholders​(String str)
    Returns a string where all placeholders are replaced with the real values.
    Configuration sync()
    Returns a new Configuration where all file sizes and checksums are synced with the real locations as listed in the current config.
    Configuration sync​(Path overrideBasePath)
    Returns a new Configuration where all file sizes and checksums are synced with the real locations as listed in the current config with the base path overriden to the given Path.
    Configuration sync​(Path overrideBasePath, PrivateKey signer)
    Returns a new Configuration where all file sizes, checksums and signatures are synced with the real locations as listed in the current config with the base path overriden to the given Path.
    Configuration sync​(PrivateKey signer)
    Returns a new Configuration where all file sizes, checksums and signatures are synced with the real locations as listed in the current config.
    String toString()
    Returns an XML string exactly as write(Writer) would output.
    boolean update()
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean update​(PublicKey key)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean update​(PublicKey key, Injectable injectable)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean update​(PublicKey key, UpdateHandler handler)
    Starts the update process by using the provided instance as the update handler.
    boolean update​(Injectable injectable)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean update​(UpdateHandler handler)
    Starts the update process by using the provided instance as the update handler.
    boolean updateTemp​(Path tempDir)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean updateTemp​(Path tempDir, PublicKey key)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean updateTemp​(Path tempDir, PublicKey key, Injectable injectable)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean updateTemp​(Path tempDir, PublicKey key, UpdateHandler handler)
    Starts the update process by using the provided instance as the update handler.
    boolean updateTemp​(Path tempDir, Injectable injectable)
    Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.
    boolean updateTemp​(Path tempDir, UpdateHandler handler)
    Starts the update process by using the provided instance as the update handler.
    void verifyConfiguration​(PublicKey key)
    Verifies this config against this public key and throws a SecurityException if the config doesn't have a signature or if verification failed.
    void write​(Writer writer)  

    Methods inherited from class Object

    clone, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Method Details

    • getTimestamp

      public Instant getTimestamp()
      Returns the timestamp this configuration was last updated using the Configuration.Builder API or sync(). This is read from the timestamp attribute in the root element. If the attribute is missing this will return null.

      It does not have any effect on the behavior of anything else; it is rather just for reference purposes (i.e. "Last Updated: 2 Weeks Ago"), or for clients willing to act according to this value.

      Returns:
      The timestamp this configuration was last updated, or @{null}, if missing from the XML.
    • getSignature

      public String getSignature()
      Returns the signature for this configuration. The signature ensures that the config has not been tampered. Changes that are not significant to the framework, as whitespaces or element/attribute order do not matter. Changing element order in lists (i.e. properties or files) do change the ordering in the end-resulting list, and is part of the signature.

      The timestamp field is never part of the signature.

      This field is read from the signature in the root node. If the attribute is missing this will return null.

      Returns:
      The signature for this configuration.
    • getBaseUri

      public URI getBaseUri()
      Returns the base URI against whom all relative URIs in individual files are resolved. The URI points to the remote (or if it has a file:/// scheme, local) location from where the file should be downloaded.

      This is read from the uri attribute from the <base> element. If the attribute is missing this will return null.

      Returns:
      The base URI, or null if missing.
    • getBasePath

      public Path getBasePath()
      Returns the base path against whom all relative paths in individual files are resolved. The path points to the location the files should be saved to on the client's local machine.

      This is read from the path attribute from the <base> element. If the attribute is missing this will return null.

      Returns:
      The base path, or null if missing.
    • getUpdateHandler

      public String getUpdateHandler()
      Returns the UpdateHandler class name that should be used instead of of the default highest version currently present in the classpath or modulepath.

      Note: This is completely optional. If this is missing the framework will automatically load the highest version currently present in the classpath or modulepath. Other than overriding the versioning resolution, it also relieves you from having to advertise them as required by the ServiceLoader class. Still, for modules you would want to add the provides directive, since this would add the module in the module graph and make the class visible to this framework.
      Please refer to Dealing with Providers for more info.

      This is read from the updateHandler attribute from the <provider> element. If the attribute is missing this will return null.

      Returns:
      The UpdateHandler class name that should be used instead of of the default highest version, or null if missing.
    • getLauncher

      public String getLauncher()
      Returns the Launcher class name that should be used instead of of the default highest version currently present in the classpath or modulepath.

      Note: This is completely optional. If this is missing the framework will automatically load the highest version currently present in the classpath or modulepath. Other than overriding the versioning resolution, it also relieves you from having to advertise them as required by the ServiceLoader class. Still, for modules you would want to add the provides directive, since this would add the module in the module graph and make the class visible to this framework.
      Please refer to Dealing with Providers for more info.

      This is read from the launcher attribute from the <provider> element. If the attribute is missing this will return null.

      Returns:
      The Launcher class name that should be used instead of of the default highest version, or null if missing.
    • getFiles

      public List<FileMetadata> getFiles()
      Returns the list of files listed in the configuration file. This will never return null.

      These are read from the <files> element.

      Returns:
      The FileMetadata instances listed in the configuration file, never null.
    • getProperties

      public List<Property> getProperties()
      Returns an unmodifiable list of properties listed in the configuration file. This will never return null.

      This is read from the <properties> element.

      Returns:
      The Property instances listed in the configuration file.
    • getProperties

      public List<Property> getProperties​(String key)
      Returns a list of properties listed in the configuration file that have the provided key. It might be more than one, if they have different operating systems. The list will never contain 2 properties with the same value returned by Property.getOs().

      The list might be empty, but never null.

      Returns:
      The Property instances listed in the configuration file that contain the provided key.
    • getResolvedProperties

      public Map<String,​String> getResolvedProperties()
      Returns an unmodifiable map of keys and values after resolving the placeholders. It includes everything from dynamic properties to system properties or environment variables. This will not include properties marked for foreign operating systems.
      Returns:
      A map of the keys and real values of the properties, after resolving the placeholders.
    • getResolvedProperty

      public String getResolvedProperty​(String key)
      Returns the real value of the property with the given key, after resolving the placeholders. It includes everything from dynamic properties to system properties or environment variables.
      Parameters:
      key - The key of the property.
      Returns:
      The real value of the property after resolving the placeholders.
    • resolvePlaceholders

      public String resolvePlaceholders​(String str)
      Returns a string where all placeholders are replaced with the real values. If the given string is null, the same value will be returned.

      If it includes a reference to a placeholder that could not be resolved, it will fail.

      Parameters:
      str - The source string to try to resolve.
      Returns:
      The resolved string, or null if null was passed.
      Throws:
      IllegalArgumentException - if the source string contains a placeholder that could not be resolved.
    • implyPlaceholders

      public String implyPlaceholders​(String str)
      Returns a string with real values replaced with placeholders. This method will never break up words, it acts exactly as:
       implyPlaceholders(str, PlaceholderMatchType.WHOLE_WORD);
       

      If the given string is null, the same value will be returned.

      Parameters:
      str - The string to attempt to replace with placeholders.
      Returns:
      The replaced string, or null if null was passed.
    • implyPlaceholders

      public String implyPlaceholders​(String str, boolean isPath)
      Returns a string with real values replaced with placeholders. This method will never break up words, it acts exactly as:
       implyPlaceholders(str, isPath, PlaceholderMatchType.WHOLE_WORD);
       

      This overload allows you to specify whether the given string is a hierarchical string in a path manner. If true, then "\\" and "/" are treated identical. This is intended to match more strings in a platform independent way.

       String old = "C:/Users/User/Desktop";
       String newString = config.implyPlaceholders(old, true);
       
       // -> newString is "${user.home}/Desktop" even though the real system
       // property value is "C:\\Users\\User" on Windows
       

      Additionally, if isPath is true, user.home and user.dir will only be matched to the beginning of the string or just after the file: URI scheme in the beginning of the string.

      If the given string is null, the same value will be returned.

      Parameters:
      str - The string to attempt to replace with placeholders.
      isPath - Whether the given string is a path like string.
      Returns:
      The replaced string, or null if null was passed.
    • implyPlaceholders

      public String implyPlaceholders​(String str, PlaceholderMatchType matchType)
      Returns a string with real values replaced with placeholders.

      You can specify how matches should be found by passing the PlaceholderMatchType.

      • EVERY_OCCURENCE — Will break words with placeholders if it finds a match.
      • WHOLE_WORD — Will only replace with placeholders if the it doesn't break a word (using regex \b word boundary).
      • FULL_MATCH — Will only replace if the complete string matches with one placeholder.

      If the given string is null, the same value will be returned.

      Parameters:
      str - The string to attempt to replace with placeholders.
      matchType - The word-breaking policy to use when matching.
      Returns:
      The replaced string, or null if null was passed.
    • implyPlaceholders

      public String implyPlaceholders​(String str, PlaceholderMatchType matchType, boolean isPath)
      Returns a string with real values replaced with placeholders.

      This overload allows you to specify whether the given string is a hierarchical string in a path manner. If true, then "\\" and "/" are treated identical. This is intended to match more strings in a platform independent way.

       String old = "C:/Users/User/Desktop";
       String newString = config.implyPlaceholders(old, true);
       
       // -> newString is "${user.home}/Desktop" even though the real system
       // property value is "C:\\Users\\User" on Windows
       

      Additionally, if isPath is true, user.home and user.dir will only be matched to the beginning of the string or just after the file: URI scheme in the beginning of the string.

      You can specify how matches should be found by passing the PlaceholderMatchType.

      • EVERY_OCCURENCE — Will break words with placeholders if it finds a match.
      • WHOLE_WORD — Will only replace with placeholders if the it doesn't break a word (using regex \b word boundary).
      • FULL_MATCH — Will only replace if the complete string matches with one placeholder.

      If the given string is null, the same value will be returned.

      Parameters:
      str - The string to attempt to replace with placeholders.
      matchType - The match policy to use.
      isPath - Whether the given string is a path like string.
      Returns:
      The replaced string, or null if null was passed.
    • requiresUpdate

      public boolean requiresUpdate() throws IOException
      Checks the metadata of every file and returns true if at-least one file requires an update, and false if no file requires an update.

      This method is completely unaware of UpdateHandler.shouldCheckForUpdate(FileMetadata), i.e. it might return true even if that method returns false for a particular file.

      Returns:
      If at-least one file requires an update.
      Throws:
      IOException - If any IOException arises while reading the files.
    • update

      public boolean update()
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Returns:
      If no error was thrown in the whole process.
    • update

      public boolean update​(UpdateHandler handler)
      Starts the update process by using the provided instance as the update handler.

      Any error that arises just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      handler - The UpdateHandler to use for process callbacks.
      Returns:
      If no error was thrown in the whole process.
    • update

      public boolean update​(Injectable injectable)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      Immediately after loading the class, it will call:

       Injectable.injectBidirectional(injectable, updateHandler);
       
      to exchange fields to and from both instances. When injection is complete it will call all methods of both instances, marked with PostInject, following the behavior documented in Injectable documentation.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      injectable - The object to use for field exchange between the bootstrap and the update handler.
      Returns:
      If no error was thrown in the whole process.
    • update

      public boolean update​(PublicKey key)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      It will use the provided PublicKey to validate signatures of each individual file. It will not validate the config's own signature.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      key - The PublicKey to validate the files' signatures.
      Returns:
      If no error was thrown in the whole process.
    • update

      public boolean update​(PublicKey key, Injectable injectable)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      Immediately after loading the class, it will call:

       Injectable.injectBidirectional(injectable, updateHandler);
       
      to exchange fields to and from both instances. When injection is complete it will call all methods of both instances, marked with PostInject, following the behavior documented in Injectable documentation.

      It will use the provided PublicKey to validate signatures of each individual file. It will not validate the config's own signature.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      key - The PublicKey to validate the files' signatures.
      injectable - The object to use for field exchange between the bootstrap and the update handler.
      Returns:
      If no error was thrown in the whole process.
    • update

      public boolean update​(PublicKey key, UpdateHandler handler)
      Starts the update process by using the provided instance as the update handler.

      It will use the provided PublicKey to validate signatures of each individual file. It will not validate the config's own signature.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      key - The PublicKey to validate the files' signatures.
      handler - The UpdateHandler to use for process callbacks.
      Returns:
      If no error was thrown in the whole process.
    • updateTemp

      public boolean updateTemp​(Path tempDir)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      It will download all files in the tempDir directory, which can later be finalized by calling Update.finalizeUpdate(Path).

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      tempDir - The location to temporarily store the downloaded files until Update.finalizeUpdate(Path) is called.
      Returns:
      If no error was thrown in the whole process.
    • updateTemp

      public boolean updateTemp​(Path tempDir, Injectable injectable)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      Immediately after loading the class, it will call:

       Injectable.injectBidirectional(injectable, updateHandler);
       
      to exchange fields to and from both instances. When injection is complete it will call all methods of both instances, marked with PostInject, following the behavior documented in Injectable documentation.

      It will download all files in the tempDir directory, which can later be finalized by calling Update.finalizeUpdate(Path).

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      tempDir - The location to temporarily store the downloaded files until Update.finalizeUpdate(Path) is called.
      injectable - The object to use for field exchange between the bootstrap and the update handler.
      Returns:
      If no error was thrown in the whole process.
    • updateTemp

      public boolean updateTemp​(Path tempDir, UpdateHandler handler)
      Starts the update process by using the provided instance as the update handler.

      It will download all files in the tempDir directory, which can later be finalized by calling Update.finalizeUpdate(Path).

      Any error that arises just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      tempDir - The location to temporarily store the downloaded files until Update.finalizeUpdate(Path) is called.
      handler - The UpdateHandler to use for process callbacks.
      Returns:
      If no error was thrown in the whole process.
    • updateTemp

      public boolean updateTemp​(Path tempDir, PublicKey key)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      It will download all files in the tempDir directory, which can later be finalized by calling Update.finalizeUpdate(Path).

      It will use the provided PublicKey to validate signatures of each individual file. It will not validate the config's own signature.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      tempDir - The location to temporarily store the downloaded files until Update.finalizeUpdate(Path) is called.
      key - The PublicKey to validate the files' signatures.
      Returns:
      If no error was thrown in the whole process.
    • updateTemp

      public boolean updateTemp​(Path tempDir, PublicKey key, Injectable injectable)
      Starts the update process by locating the class returned by getUpdateHandler() or -- if it returns null -- the registered highest version UpdateHandler or DefaultUpdateHandler if non were found.

      Immediately after loading the class, it will call:

       Injectable.injectBidirectional(injectable, updateHandler);
       
      to exchange fields to and from both instances. When injection is complete it will call all methods of both instances, marked with PostInject, following the behavior documented in Injectable documentation.

      It will download all files in the tempDir directory, which can later be finalized by calling Update.finalizeUpdate(Path).

      It will use the provided PublicKey to validate signatures of each individual file. It will not validate the config's own signature.

      Any error that arises once the update handler was loaded just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      tempDir - The location to temporarily store the downloaded files until Update.finalizeUpdate(Path) is called.
      key - The PublicKey to validate the files' signatures.
      injectable - The object to use for field exchange between the bootstrap and the update handler.
      Returns:
      If no error was thrown in the whole process.
    • updateTemp

      public boolean updateTemp​(Path tempDir, PublicKey key, UpdateHandler handler)
      Starts the update process by using the provided instance as the update handler.

      It will download all files in the tempDir directory, which can later be finalized by calling Update.finalizeUpdate(Path).

      It will use the provided PublicKey to validate signatures of each individual file. It will not validate the config's own signature.

      Any error that arises just get's passed to UpdateHandler.failed(Throwable) and this method returns false. An exception thrown in UpdateHandler.failed(Throwable), UpdateHandler.succeeded() or UpdateHandler.stop() will be thrown back to the caller of this method.

      This method is intended to be used on the client machine only.

      Parameters:
      tempDir - The location to temporarily store the downloaded files until Update.finalizeUpdate(Path) is called.
      key - The PublicKey to validate the files' signatures.
      handler - The UpdateHandler to use for process callbacks.
      Returns:
      If no error was thrown in the whole process.
    • launch

      public void launch()
      Launches the business application by loading all files marked with the classpath or modulepath attributes, on their respective paths, dynamically.

      It will then locate the class returned by getLauncher() or -- if it returns null -- the registered highest version Launcher or DefaultLauncher if non were found.

      It will then call Launcher.run(LaunchContext) on a new thread, and block the caller of this method until run() returns. New threads spawned by the run() method will not block.

      This method is intended to be used on the client machine only.

    • launch

      public void launch​(Injectable injectable)
      Launches the business application by loading all files marked with the classpath or modulepath attributes, on their respective paths, dynamically.

      It will then locate the class returned by getLauncher() or -- if it returns null -- the registered highest version Launcher or DefaultLauncher if non were found.

      Immediately after loading the class, it will call:

       Injectable.injectBidirectional(injectable, launcher);
       
      to exchange fields to and from both instances. When injection is complete it will call all methods of both instances, marked with PostInject, following the behavior documented in Injectable documentation.

      It will then call Launcher.run(LaunchContext) on a new thread, and block the caller of this method until run() returns. New threads spawned by the run() method will not block.

      This method is intended to be used on the client machine only.

      Parameters:
      injectable - The object to use for field exchange between the bootstrap and the launcher.
    • launch

      public void launch​(Launcher launcher)
      Launches the business application by loading all files marked with the classpath or modulepath attributes, on their respective paths, dynamically.

      It will then call Launcher.run(LaunchContext) on a new thread, and block the caller of this method until run() returns. New threads spawned by the run() method will not block.

      This method is intended to be used on the client machine only.

      Parameters:
      launcher - The launcher to use as the business application entry point
    • deleteOldFiles

      public void deleteOldFiles​(Configuration oldConfig) throws IOException
      Convenience method to delete files only present in oldConfig and clean up app directory.

      Caution: This method does not guarantee all files are actually removed. Many things can go wrong and new updates should never rely on this operation. Don't release new bootstrap modules with existing module or package names (by marking ignoreBootConflict to true) even if the old "should" be deleted here. For service providers, increment the version() to let update4j know it should select the new, even if the old is deleted here.

      A file in the old configuration is considered "old" if that file:

      • Exists.
      • Is not present in the current configuration. It will query the underlying operating system to check equality instead of comparing path names.
      • If the file's checksum matches the checksum listed in the old config. This is an extra - optional - layer of safety to prevent unwanted files from being deleted. You can turn off this check by calling deleteOldFiles(Configuration, boolean, int) instead.

      Files that are not marked with either classpath or modulepath in the config, will be assumed to run in the bootstrap; therefore will not be deleted immediately. Instead, they will be queued to be deleted when the JVM shuts down by spawning a new system-dependent process with 5 seconds delay (you can change the delay by calling deleteOldFiles(Configuration, boolean, int) instead. Files that are marked with classpath or modulepath will try to be deleted immediately. If it fails (e.g. you called this method from the business application and the files are locked by operating system), it will queue them together with the bootstrap files.

      Please note: Long running shutdown hooks may keep files locked thus preventing them from being deleted. Call deleteOldFiles(Configuration, boolean, int) and increase the secondsDelay to ensure it runs after all shutdown hooks completed.

      You must not call this method if:

       this.requiresUpdate() == true
       
      in other words, you must first update the current config, or if using updateTemp() you must first call Update.finalizeUpdate(Path). If you return false in UpdateHandler.shouldCheckForUpdate(FileMetadata) for a particular file, you cannot use this method out of the box. You can hand-modify the config to strip those files by removing them with generateXmlMapper(). Consult the Manual XML Manipulation section in this class JavaDoc.
      Parameters:
      oldConfig - The old configuration.
      Throws:
      IllegalStateException - If this method is called but the current configuration is not up-to-date.
      IOException - If checking if current config is up-to-date, checking file equality, or calculating checksum failed.
    • deleteOldFiles

      public void deleteOldFiles​(Configuration oldConfig, boolean matchChecksum, int secondsDelay) throws IOException
      Convenience method to delete files only present in oldConfig and clean up app directory.

      Caution: This method does not guarantee all files are actually removed. Many things can go wrong and new updates should never rely on this operation. Don't release new bootstrap modules with existing module or package names (by marking ignoreBootConflict to true) even if the old "should" be deleted here. For service providers, increment the version() to let update4j know it should select the new, even if the old is deleted here.

      A file in the old configuration is considered "old" if that file:

      • Exists.
      • Is not present in the current configuration. It will query the underlying operating system to check equality instead of comparing path names.
      • If matchChecksum is true — if the file's checksum matches the checksum listed in the old config. This is an extra - optional - layer of safety to prevent unwanted files from being deleted.

      Files that are not marked with either classpath or modulepath in the config, will be assumed to run in the bootstrap; therefore will not be deleted immediately. Instead, they will be queued to be deleted when the JVM shuts down by spawning a new system-dependent process with secondsDelay seconds delay. Files that are marked with classpath or modulepath will try to be deleted immediately. If it fails (e.g. you called this method from the business application and the files are locked by operating system), it will queue them together with the bootstrap files.

      Please note: Long running shutdown hooks may keep files locked thus preventing them from being deleted. Increase the secondsDelay to ensure it runs after all shutdown hooks completed. secondsDelay will never be less than 1; smaller values will be adjusted.

      You must not call this method if:

       this.requiresUpdate() == true
       
      in other words, you must first update the current config, or if using updateTemp() you must first call Update.finalizeUpdate(Path). If you return false in UpdateHandler.shouldCheckForUpdate(FileMetadata) for a particular file, you cannot use this method out of the box. You can hand-modify the config to strip those files by removing them with generateXmlMapper(). Consult the Manual XML Manipulation section in this class JavaDoc.
      Parameters:
      oldConfig - The old configuration.
      matchChecksum - Whether checksums should be checked and delete only if matching.
      secondsDelay - Second to delay deletion after JVM shut down. If less the 1, it will be adjusted to 1.
      Throws:
      IllegalStateException - If this method is called but the current configuration is not up-to-date.
      IOException - If checking if current config is up-to-date, checking file equality, or calculating checksum failed.
    • getOldFiles

      public List<FileMetadata> getOldFiles​(Configuration oldConfig, boolean matchChecksum) throws IOException
      Returns a list of files of old files present in oldConfig but not in the current.

      A file in the old configuration is considered "old" if that file:

      • Exists.
      • Is not present in the current configuration. It will query the underlying operating system to check equality instead of comparing path names.
      • If matchChecksum is true — if the file's checksum matches the checksum listed in the old config.

      Old files are assumed safe to be removed with deleteOldFiles(Configuration, boolean, int).

      Parameters:
      oldConfig - The old configuration.
      matchChecksum - Whether checksums should be matching in order to consider it old.
      Returns:
      A list of old files.
      Throws:
      IOException - If checking file equality, or calculating checksum failed.
    • read

      public static Configuration read​(Reader reader) throws IOException
      Reads and parses a configuration XML.
      Parameters:
      reader - The Reader for reading the XML.
      Returns:
      A Configuration as parsed from the given XML.
      Throws:
      IOException - Any exception that arises while reading.
    • read

      public static Configuration read​(Reader reader, Map<String,​String> dynamicProperties) throws IOException
      Reads and parses a configuration XML, and add the provided properties.
      Parameters:
      reader - The Reader for reading the XML.
      dynamicProperties - Unlisted properties to override listed properties or to map unmapped placeholders.
      Returns:
      A Configuration as parsed from the given XML.
      Throws:
      IOException - Any exception that arises while reading.
    • read

      public static Configuration read​(Reader reader, PublicKey key) throws IOException
      Reads and parses a configuration XML, then verifies the configuration signature against the public key.
      Parameters:
      reader - The Reader for reading the XML.
      dynamicProperties - Unlisted properties to override listed properties or to map unmapped placeholders.
      Returns:
      A Configuration as parsed from the given XML.
      Throws:
      IOException - Any exception that arises while reading.
      SecurityException - If the configuration does not have a signature, or if verification failed.
    • read

      public static Configuration read​(Reader reader, PublicKey key, Map<String,​String> dynamicProperties) throws IOException
      Reads and parses a configuration XML and add more properties, then verifies the configuration signature against the public key.
      Parameters:
      reader - The Reader for reading the XML.
      key - The public key to verify the config's signature against.
      dynamicProperties - Unlisted properties to override listed properties or to map unmapped placeholders.
      Returns:
      A Configuration as parsed from the given XML.
      Throws:
      IOException - Any exception that arises while reading.
      SecurityException - If the configuration does not have a signature, or if verification failed.
    • parse

      public static Configuration parse​(ConfigMapper mapper)
      Parses a configuration from the given XML mapper.
      Parameters:
      mapper - The mapper to parse.
      Returns:
      A Configuration as parsed from the mapper.
    • parse

      public static Configuration parse​(ConfigMapper mapper, Map<String,​String> dynamicProperties)
      Parses a configuration from the given XML mapper, and add the provided properties.
      Parameters:
      mapper - The mapper to parse.
      dynamicProperties - Unlisted properties to override listed properties or to map unmapped placeholders.
      Returns:
      A Configuration as parsed from the mapper.
    • sync

      public Configuration sync() throws IOException
      Returns a new Configuration where all file sizes and checksums are synced with the real locations as listed in the current config. If changes were made it will also update the timestamp.

      This method is intended to be used on the development/build machine only to draft a new release when changes are made to files but it's still the same files.

      If you want to change the files you should generally use the Builder API. If you want, you can manually add or remove files in the config file; for new files, just put the path and call sync to automatically fill the rest.

      Returns:
      A new Configuration with synced file metadata.
      Throws:
      IOException - If any exception arises while reading file metadata.
    • sync

      public Configuration sync​(Path overrideBasePath) throws IOException
      Returns a new Configuration where all file sizes and checksums are synced with the real locations as listed in the current config with the base path overriden to the given Path. This is generally used to point to the build output location. Files listed with an explicit absolute path will not be overriden. If changes were made it will also update the timestamp.

      This method is intended to be used on the development/build machine only to draft a new release when changes are made to files but it's still the same files.

      If you want to change the files you should generally use the Builder API. If you want, you can manually add or remove files in the config file; for new files, just put the path and call sync to automatically fill the rest.

      Parameters:
      overrideBasePath - The Path to use instead of the base path to lookup files.
      Returns:
      A new Configuration with synced file metadata.
      Throws:
      IOException - If any exception arises while reading file metadata
    • sync

      public Configuration sync​(PrivateKey signer) throws IOException
      Returns a new Configuration where all file sizes, checksums and signatures are synced with the real locations as listed in the current config. If changes were made it will also update the timestamp.

      This method is intended to be used on the development/build machine only to draft a new release when changes are made to files but it's still the same files.

      If you want to change the files you should generally use the Builder API. If you want, you can manually add or remove files in the config file; for new files, just put the path and call sync to automatically fill the rest.

      Parameters:
      signer - The PrivateKey to use for config and file signing.
      Returns:
      A new Configuration with synced file metadata.
      Throws:
      IOException - If any exception arises while reading file metadata.
    • sync

      public Configuration sync​(Path overrideBasePath, PrivateKey signer) throws IOException
      Returns a new Configuration where all file sizes, checksums and signatures are synced with the real locations as listed in the current config with the base path overriden to the given Path. This is generally used to point to the build output location. Files listed with an explicit absolute path will not be overriden. If changes were made it will also update the timestamp.

      This method is intended to be used on the development/build machine only to draft a new release when changes are made to files but it's still the same files.

      If you want to change the files you should generally use the Builder API. If you want, you can manually add or remove files in the config file; for new files, just put the path and call sync to automatically fill the rest.

      Parameters:
      overrideBasePath - The Path to use instead of the base path to lookup files.
      signer - The PrivateKey to use for config and file signing.
      Returns:
      A new Configuration with synced file metadata.
      Throws:
      IOException - If any exception arises while reading file metadata
    • verifyConfiguration

      public void verifyConfiguration​(PublicKey key)
      Verifies this config against this public key and throws a SecurityException if the config doesn't have a signature or if verification failed.

      This process does not check individual file signatures.

      Parameters:
      key - The public key to check against.
    • generateXmlMapper

      public ConfigMapper generateXmlMapper()
      Generates a new XML mapper for direct XML manipulation with values populated identical to this configuration. More formally:
       this.equals(Configuration.parse(this.generateXmlMapper())) == true
       

      Any change to the mapper has no effect to the current configuration. A new copy is created on each call.

      Returns:
      A new XML mapper with values from this configuration.
    • write

      public void write​(Writer writer) throws IOException
      Throws:
      IOException
    • toString

      public String toString()
      Returns an XML string exactly as write(Writer) would output.
      Overrides:
      toString in class Object
      Returns:
      An XML string exactly as write(Writer) would output.
    • equals

      public boolean equals​(Object other)
      Returns whether the given configuration is equals to this. More formally:
       this.equals(other) == this.toString().equals(other.toString())
       
      Overrides:
      equals in class Object
      Returns:
      Whether the given configuration is equals to this.
    • builder

      public static Configuration.Builder builder()
      The entry point to the Builder API.

      This should only be used on the development/build machine when drafting a new release. It should not be used to load a config on the client side.

      Returns:
      A configuration builder.