[java-identity-provider] 07/11: IDP-1499 New V4 Installer: First cut of install task

Rod Widdowson rdw at steadingsoftware.com
Fri Oct 11 11:08:30 EDT 2019


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=071ea3c9363e968c1c58d42b8c48f0e50ce84ad8

commit 071ea3c9363e968c1c58d42b8c48f0e50ce84ad8
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Fri Oct 11 14:05:48 2019 +0100

    IDP-1499 New V4 Installer: First cut of install task
    
    https://issues.shibboleth.net/jira/browse/IDP-1499
    
    Includes
      - Most of the install work (metdata, reprotect still tbd)
      - Separate InstallProperties (extensible) from CurrentInstallState (not)
      - Silly test program to drive this
      - Eclipse-local debugging
---
 idp-installer/pom.xml                              |  18 +-
 .../net/shibboleth/idp/installer/impl/AntRun.java  |  74 ++++++
 .../shibboleth/idp/installer/impl/BuildWar.java    |  16 +-
 ...opyDistributions.java => CopyDistribution.java} |  52 ++--
 .../idp/installer/impl/CurrentInstallState.java    | 115 +++++++++
 .../idp/installer/impl/InstallerProperties.java    | 113 ++++-----
 .../idp/installer/impl/InstallerSupport.java       |   7 +-
 .../idp/installer/impl/KeyManagement.java          |  85 +++++--
 .../idp/installer/impl/PropertiesWithComments.java |  37 ++-
 .../shibboleth/idp/installer/impl/V4Install.java   | 266 +++++++++++++++++++++
 .../shibboleth/idp/installer/{ => impl}/Test.java  |   0
 11 files changed, 666 insertions(+), 117 deletions(-)

diff --git a/idp-installer/pom.xml b/idp-installer/pom.xml
index b27c49f..998811e 100644
--- a/idp-installer/pom.xml
+++ b/idp-installer/pom.xml
@@ -47,12 +47,6 @@
             <scope>compile</scope><!-- normally runtime -->
         </dependency>
 
-<!--        <dependency>
-            <groupId>ch.qos.logback</groupId>
-            <artifactId>logback-classic</artifactId>
-            <scope>compile</scope>
-        </dependency> -->
-
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-core</artifactId>
@@ -113,7 +107,17 @@
             <artifactId>opensaml-saml-impl</artifactId>
             <scope>test</scope>
         </dependency>
-
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
         <!-- Managed Dependencies -->
     </dependencies>
 
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/AntRun.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/AntRun.java
new file mode 100644
index 0000000..f3729f6
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/AntRun.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.impl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * code to tode over durig testing.
+ */
+public final class AntRun {
+    
+    /** hidden  Constructor. */
+    private AntRun() {}
+
+    /** simulate the ant tasks.
+     * @param args what
+     * @throws ComponentInitializationException 
+     */
+    public static void main(final String[] args) throws ComponentInitializationException {
+        final Logger log = LoggerFactory.getLogger(AntRun.class);
+        if (args.length !=1) {
+            log.error("One Parameter only {}", (Object[]) args);
+            return;
+        }
+        boolean copyInstall = false;
+        boolean doInstall = false;
+        if ("install".equals(args[0])) {
+            copyInstall = true;
+            doInstall = true;
+        } else if ("install-nocopy".equals(args[0])) {
+            doInstall = true;
+        } else if (!"build-war".equals(args[0])) {
+            log.error("Parameter must be \"install\", \"install-nocopy\" or \"build-war\" was \"{}\"", args[0]);
+            return;
+        }
+        final InstallerProperties ip = new InstallerProperties(!copyInstall);
+        ip.initialize();
+        final CurrentInstallState is = new CurrentInstallState(ip);
+        is.initialize();
+
+        if (copyInstall) {
+            final CopyDistribution dist = new CopyDistribution(ip, is);
+            dist.execute();
+        }
+        
+        if (doInstall) {
+            final V4Install inst = new V4Install(ip, is);
+            inst.execute();
+        }
+        
+        final BuildWar bw = new BuildWar(ip, is);
+        bw.execute();
+        
+    }
+
+}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/BuildWar.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/BuildWar.java
index 53ab077..9537fe2 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/BuildWar.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/BuildWar.java
@@ -26,6 +26,9 @@ import org.apache.tools.ant.taskdefs.Jar;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
 /** Code to build the war file during an install or on request.<p/>
  * This code<ul>
  * <li>Deletes any old detritus</li>
@@ -36,7 +39,7 @@ import org.slf4j.LoggerFactory;
  * <li>Deletes webapp.tmp</li>
  * </ul>
  */
-public class BuildWar {
+public class BuildWar extends AbstractInitializableComponent {
 
     /** Log. */
     private final Logger log = LoggerFactory.getLogger(BuildWar.class);
@@ -44,11 +47,18 @@ public class BuildWar {
     /** Properties for the job. */
     private final InstallerProperties installerProps;
 
+    /** Current Install. */
+    private final CurrentInstallState currentState;
+
     /** Constructor.
      * @param props The environment for the work.
+     * @param installState  Where we are right now.
      */
-    public BuildWar(final InstallerProperties props) {
+    public BuildWar(final InstallerProperties props, final CurrentInstallState installState) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(props);
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(installState);
         installerProps = props;
+        currentState = installState;
     }
 
     /** Method to do the work of building the war.
@@ -58,7 +68,7 @@ public class BuildWar {
         final Path target = installerProps.getTargetDir();
         final Path warFile = target.resolve("war").resolve("idp.war");
 
-        log.info("Rebuilding {}", warFile.toAbsolutePath());
+        log.info("Rebuilding {}, Version", warFile.toAbsolutePath(), currentState.getInstalledVersion());
         try {
             DeletingVisitor.deleteTree(target.resolve("webpapp"));
         } catch (final IOException e) {
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CopyDistributions.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CopyDistribution.java
similarity index 78%
rename from idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CopyDistributions.java
rename to idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CopyDistribution.java
index 48a377d..bff7c1d 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CopyDistributions.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CopyDistribution.java
@@ -20,41 +20,49 @@ package net.shibboleth.idp.installer.impl;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.text.SimpleDateFormat;
 import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
 
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.taskdefs.Copy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
 /**
  * Copy the distribution to its final location.
  */
-public final class CopyDistributions {
+public final class CopyDistribution extends AbstractInitializableComponent {
 
     /** Log. */
-    private final Logger log = LoggerFactory.getLogger(CopyDistributions.class);
+    private final Logger log = LoggerFactory.getLogger(CopyDistribution.class);
 
     /** Properties for the job. */
-    private final InstallerProperties installerProps;
+    @Nonnull private final InstallerProperties installerProps;
 
     /** Constructor.
      * @param props The environment for the work.
+     * @param installState  Where we are right now.
      */
-    public CopyDistributions(final InstallerProperties props) {
+    public CopyDistribution(@Nonnull final InstallerProperties props, @Nonnull final CurrentInstallState installState) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(props);
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(installState);
         installerProps = props;
     }
 
     /** Copy the distribution from the dstribution to its new location.
-     * @param ip what drives the install.
      * @throws BuildException if badness occurs
      */
-    public void execute(final InstallerProperties ip) throws BuildException {
+    public void execute() throws BuildException {
         backupOld();
         deleteOld();
         copyDist();
         copyBinDocSystem();
-        createUserFolders();
     }
 
     /** Helper for the {@link #backupOld(InstallerProperties)} method.
@@ -73,7 +81,8 @@ public final class CopyDistributions {
      * @throws BuildException if badness occurs
      */
     protected void backupOld() throws BuildException {
-        final Path backup = installerProps.getTargetDir().resolve("Old-" + Instant.now().toString());
+        final SimpleDateFormat fmt = new SimpleDateFormat("'old-'yyyy-MM-dd-HH-mm-ss");
+        final Path backup = installerProps.getTargetDir().resolve(fmt.format(Date.from(Instant.now())));
         InstallerSupport.createDirectory(backup);
         backup(installerProps.getTargetDir().resolve("edit-webapp"), backup.resolve("edit-webapp"));
         backup(installerProps.getTargetDir().resolve("doc"), backup.resolve("doc"));
@@ -85,9 +94,10 @@ public final class CopyDistributions {
      */
     private void delete(final Path what) {
         if (!Files.exists(what)) {
-            log.debug("{} doesn't exist, ignoring", what);
-        } else if (Files.isDirectory(what)) {
-            throw new BuildException("Corrupt install " + what + " is not a directory");
+            log.debug("{} doesn't exist, nothing to delete", what);
+        } else if (!Files.isDirectory(what)) {
+            log.error("Corrupt install {} is not a directory", what);
+            throw new BuildException("Corrupt install - not a directory");
         } else {
             log.debug("Deleteing {} ", what);
             try {
@@ -108,7 +118,7 @@ public final class CopyDistributions {
         delete(installerProps.getTargetDir().resolve("doc"));
         final Path system = installerProps.getTargetDir().resolve("system");
         if (Files.exists(system)) {
-            log.debug("Clearing  {} readonly (id Windows)", system);
+            log.debug("Clearing  {} readonly (if Windows)", system);
             InstallerSupport.setReadOnly(system, false);
         }
         delete(system);
@@ -136,7 +146,7 @@ public final class CopyDistributions {
     protected void copyDist() {
         final Path dist = installerProps.getTargetDir().resolve("dist");
         InstallerSupport.createDirectory(dist);
-        final Path src = installerProps.getSourceDir().resolve("dist");
+        final Path src = installerProps.getSourceDir();
         if (!Files.exists(src)) {
             log.error("Source distribution {} not found", src);
             throw new BuildException("Source distribution not found");
@@ -156,20 +166,4 @@ public final class CopyDistributions {
         distCopy(installerProps.getSourceDir(), installerProps.getTargetDir(), "doc");
         distCopy(installerProps.getSourceDir(), installerProps.getTargetDir(), "system");
     }
-
-    /** Create (if they do not exist) the user editable folders, suitable for
-     * later population during update or install.
-     * @throws BuildException if badness occurs
-     */
-    protected void createUserFolders() {
-        final Path target = installerProps.getTargetDir();
-        InstallerSupport.createDirectory(target.resolve("conf"));
-        InstallerSupport.createDirectory(target.resolve("credentials"));
-        InstallerSupport.createDirectory(target.resolve("flows"));
-        InstallerSupport.createDirectory(target.resolve("logs"));
-        InstallerSupport.createDirectory(target.resolve("messages"));
-        InstallerSupport.createDirectory(target.resolve("metadata"));
-        InstallerSupport.createDirectory(target.resolve("views"));
-        InstallerSupport.createDirectory(target.resolve("war"));
-    }
 }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CurrentInstallState.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CurrentInstallState.java
new file mode 100644
index 0000000..7eb918b
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/CurrentInstallState.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.impl;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import javax.annotation.Nullable;
+
+import org.apache.tools.ant.BuildException;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Tells the installers about the current install state. */
+final class CurrentInstallState extends AbstractInitializableComponent {
+
+    /** Where we are installing to. */
+    private final Path targetDir;
+    
+    /** Whether the IdP properties file exists.*/
+    private boolean idpPropertiesPresent;
+
+    /** Whether the LDAP properties file exists.*/
+    private boolean ldapPropertiesPresent;
+    
+    /** Old Version. */
+    private String oldVersion;
+    
+    /** Constructor.
+     * @param installerProps the installer situation.
+     */
+    protected CurrentInstallState(final InstallerProperties installerProps) {
+        targetDir = installerProps.getTargetDir();
+    }
+    
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        idpPropertiesPresent = Files.exists(targetDir.resolve("conf").resolve("idp.properties"));
+        ldapPropertiesPresent = Files.exists(targetDir.resolve("conf").resolve("ldap.properties"));
+        final Path conf = targetDir.resolve("conf");
+        if (!Files.exists(conf.resolve("relying-party.xml"))) {
+            // No relying party, no install
+            oldVersion = null;
+            return;
+        }
+        
+        if (!Files.exists(conf.resolve("idp.properties"))) {
+            throw new ComponentInitializationException("V2 Installation detected");
+        }
+
+        final Path currentInstall = targetDir.resolve("dist").resolve(InstallerSupport.VERSION_NAME);
+        if (!Files.exists(currentInstall)) {
+            oldVersion= "3";
+            return;
+        }
+        final Properties vers = new Properties(1);
+        try {
+            vers.load(new FileInputStream(currentInstall.toFile()));
+        } catch (final IOException e) {
+            LoggerFactory.getLogger(CurrentInstallState.class).
+                error("Could not load {}", currentInstall.toAbsolutePath(), e);
+            throw new ComponentInitializationException(e);
+        }
+        oldVersion = vers.getProperty(InstallerSupport.VERSION_NAME);
+        if (null == oldVersion) {
+            LoggerFactory.getLogger(CurrentInstallState.class).
+            error("Failed loading {}", currentInstall.toAbsolutePath());
+            throw new ComponentInitializationException("File " + InstallerSupport.VERSION_NAME +
+                    " did not contain property " + InstallerSupport.VERSION_NAME);
+        }
+    }
+
+    /** What is the installer version.
+     * @return "3" for a V3 install, null for a new install or the value we write during last install.
+     * @throws BuildException if we find an inconsiostency
+     */
+    @Nullable protected String getInstalledVersion() {
+        return oldVersion;
+    }
+    
+    /** Was idp.properties present in the target file when we started the install?
+     * @return if it was.
+     */
+    protected boolean isIdPPropertiesPresent() {
+        return idpPropertiesPresent;
+    }
+
+    /** Was ldapp.properties present in the target file when we started the install?
+     * @return if it was.
+     */
+    protected boolean isLDAPPropertiesPresent() {
+        return ldapPropertiesPresent;
+    }
+}
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java
index 62c2671..b2c917a 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerProperties.java
@@ -35,6 +35,7 @@ import javax.annotation.Nullable;
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.input.DefaultInputHandler;
 import org.apache.tools.ant.input.InputRequest;
+import org.apache.tools.ant.launch.Launcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,12 +68,9 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
  *            services.merge.properties: The name of a property file to merge with services.properties</li>
  *       <li> if idp.is.V2 is set, then this file should contain a line setting
  *            idp.service.relyingparty.resources=shibboleth.LegacyRelyingPartyResolverResources</li></ul>
- * <li> nameid.merge.properties: The name of a property file to merge with saml-nameid.properties.
- *             If idp.is.V2 is set, then this file should contain lines enabling legacy nameid generation
  * <li> idp.property.file: The name of a property file to fill in some or all of the above.
               This file is deleted after processing.</li>
  * <li> idp.no.tidy: Do not delete the two above files (debug only)</li>
- * <li> idp.jetty.config: Copy jetty configuration from distribution (Unsupported)</li>
  * <li> ldap.merge.properties:  The name of a property file to merge with ldap.properties</li>
  * <li> idp.conf.filemode (default "600"): The permissions to mark the files in conf with (UNIX only).</li>
  * <li> idp.conf.credentials.filemode (default "600"): The permissions to mark the files in conf with (UNIX only).</li>
@@ -82,11 +80,17 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 public class InstallerProperties extends AbstractInitializableComponent {
 
     /** The base directory, inherited and shared with ant. */
-    public static final String ANT_BASE_DIR = "ant.home";
+    public static final String ANT_BASE_DIR = Launcher.ANTHOME_PROPERTY;
 
     /** The name of a property file to fill in some or all of the above. This file is deleted after processing. */
     public static final String PROPERTY_SOURCE_FILE = "idp.property.file";
 
+    /** The name of a property file to merge with idp.properties. */
+    public static final String IDP_PROPERTIES_MERGE = "idp.merge.properties";
+
+    /** The name of a property file to merge with ldap.properties. */
+    public static final String LDAP_PROPERTIES_MERGE = "ldap.merge.properties";
+
     /** Where to install to.  Default is basedir */
     public static final String TARGET_DIR = "idp.target.dir";
 
@@ -114,9 +118,12 @@ public class InstallerProperties extends AbstractInitializableComponent {
     /** The sealer alias to use.  */
     public static final String SEALER_ALIAS = "idp.sealer.alias";
 
-    /** The sealer alias to use.  */
+    /** The the key size to generate.  */
     public static final String KEY_SIZE = "idp.keysize";
 
+    /** Whether to tidy up after ourselves. */
+    public static final String NO_TIDY = "idp.no.tidy";
+
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(InstallerProperties.class);
 
@@ -126,9 +133,6 @@ public class InstallerProperties extends AbstractInitializableComponent {
     /** The properties driving the install. */
     @NonnullAfterInit private Properties installerProperties;
 
-    /** The file specified in the system file idp.property.file (if present). */
-    @Nullable private File idpPropertyFile;
-
     /** The target Directory. */
     private Path targetDir;
 
@@ -144,11 +148,6 @@ public class InstallerProperties extends AbstractInitializableComponent {
     /** The entity ID. */
     private String entityID;
 
-    /** Old Version. */
-    private String oldVersion;
-    /** Have done the work aroun the old version ?*/
-    private boolean oldVersionDefinitive;
-
     /** Hostname. */
     private String hostname;
 
@@ -167,8 +166,8 @@ public class InstallerProperties extends AbstractInitializableComponent {
     /** Key Size. (for signing, encryption and backchannel). */
     private int keySize;
 
-    /** Whether the properties file exists.*/
-    private boolean idpPropertiesPresent;
+    /** whether to tidy up. */
+    private boolean tidy = true;
 
     /**
      * Constructor.
@@ -195,6 +194,7 @@ public class InstallerProperties extends AbstractInitializableComponent {
             throw new ComponentInitializationException(ANT_BASE_DIR + " must exist");
         }
         log.debug("base dir {}", baseDir);
+        tidy = installerProperties.get(NO_TIDY) == null;
 
         if (installerProperties.containsKey(PROPERTY_SOURCE_FILE)) {
             final Path file = baseDir.resolve(installerProperties.getProperty(PROPERTY_SOURCE_FILE));
@@ -204,14 +204,17 @@ public class InstallerProperties extends AbstractInitializableComponent {
             }
             log.debug("Loading properties from {}", file.toAbsolutePath());
 
-            idpPropertyFile = file.toFile();
+            /** The file specified in the system file idp.property.file (if present). */
+            final File idpPropertyFile = file.toFile();
             try {
                 installerProperties.load(new FileInputStream(idpPropertyFile));
             } catch (final IOException e) {
                 log.error("Could not load {}", file.toAbsolutePath(), e);
                 throw new ComponentInitializationException(e);
             }
-            idpPropertyFile.deleteOnExit();
+            if (tidy) {
+                idpPropertyFile.deleteOnExit();
+            }
         }
 
         String value = installerProperties.getProperty(NO_PROMPT);
@@ -219,7 +222,7 @@ public class InstallerProperties extends AbstractInitializableComponent {
 
         if (needSourceDir) {
             value = getValue(SOURCE_DIR, "Source (Distribution) Directory (press <enter> to accept default):",
-                    () -> antBase);
+                    () -> baseDir.toString());
             srcDir = Path.of(value);
             log.debug("Source directory {}", srcDir.toAbsolutePath());
         }
@@ -229,7 +232,6 @@ public class InstallerProperties extends AbstractInitializableComponent {
         } else {
             keySize = Integer.parseInt(installerProperties.getProperty(KEY_SIZE));
         }
-        idpPropertiesPresent = Files.exists(getTargetDir().resolve("conf").resolve("idp.properties"));
     }
 
     /** Lookup a property.  If it isn't defined then ask the user (if we are allowed)
@@ -312,43 +314,6 @@ public class InstallerProperties extends AbstractInitializableComponent {
         return srcDir;
     }
 
-    /** What is the installer version.
-     * @return "3" for a V3 install, null for a new install or the value we write during last install.
-     */
-    @Nullable public String getInstallerVersion() {
-        if (oldVersionDefinitive) {
-            return oldVersion;
-        }
-        oldVersionDefinitive = true;
-        final Path conf = getTargetDir().resolve("conf");
-        if (!Files.exists(conf.resolve("relying-party.xml"))) {
-            // No relying party, no install
-            oldVersion = null;
-            return null;
-        }
-        if (!Files.exists(conf.resolve("idp.properties"))) {
-            throw new BuildException("V2 Installation detected");
-        }
-
-        final Path currentInstall = getTargetDir().resolve("dist").resolve(InstallerSupport.VERSION_NAME);
-        if (!Files.exists(currentInstall)) {
-            oldVersion= "3";
-            return oldVersion;
-        }
-        final Properties vers = new Properties(1);
-        try {
-            vers.load(new FileInputStream(currentInstall.toFile()));
-        } catch (final IOException e) {
-            log.error("Could not load {}", currentInstall.toAbsolutePath(), e);
-            throw new BuildException(e);
-        }
-        oldVersion = vers.getProperty(InstallerSupport.VERSION_NAME);
-        if (null == oldVersion) {
-            throw new BuildException("File " + InstallerSupport.VERSION_NAME +
-                    " did not contain property " + InstallerSupport.VERSION_NAME);
-        }
-        return oldVersion;
-    }
 
     /** Get the host name for this install. Defaults to information pulled from the network.
      * @return the host name.*/
@@ -498,10 +463,38 @@ public class InstallerProperties extends AbstractInitializableComponent {
         return keySize;
     }
 
-    /** Was idp.properties present in the target file when we started the install?
-     * @return if it was.
+    /** Get the property file as a File, or null if it doesn't exist.
+     * Also delete it at the end if we are deleting.
+     * @param propName the name to lookup;
+     * @return null if the property is not provided a {@link File} otherwise
+     * @throws BuildException if the property is supplied but the file doesn't exist.
+     */
+    private File getMergePropertiesFile(final String propName) throws BuildException {
+        final String propValue = installerProperties.getProperty(propName);
+        if (propValue == null) {
+            return null;
+        }
+        final Path path = baseDir.resolve(propValue);
+        if (!Files.exists(path)) {
+            log.error("Could not find specified property file {}", path );
+            throw new BuildException("Property file not found");
+        }
+        return path.toFile();
+    }
+
+    /** Get the file pointed to by {@link #IDP_PROPERTIES_MERGE}.
+     * @return the file or null if it wasn't specified
+     * @throws BuildException if the property is supplied but the file doesn't exist.
+     */
+    public File getIdPMergePropertiesFile() throws BuildException {
+        return getMergePropertiesFile(IDP_PROPERTIES_MERGE);
+    }
+
+    /** Get the file pointed to by {@link #LDAP_PROPERTIES_MERGE}.
+     * @return the file or null if it wasn't specified
+     * @throws BuildException if the property is supplied but the file doesn't exist.
      */
-    public boolean isIdPPropertiesPresent() {
-        return idpPropertiesPresent;
+    public File getLDAPMergePropertiesFile() throws BuildException {
+        return getMergePropertiesFile(LDAP_PROPERTIES_MERGE);
     }
 }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerSupport.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerSupport.java
index e8bb479..dd9fb2b 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerSupport.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/InstallerSupport.java
@@ -40,6 +40,9 @@ public final class InstallerSupport {
     /** The name of the file and the property with the current V4 installation value.*/
     public static final String VERSION_NAME = "idp.installed.version";
 
+    /** The name of the file and the property with the previous installation value.*/
+    public static final String PREVIOUS_VERSION_NAME = "idp.previous.installed.version";
+
     /** A psuedo ant-project as parent. */
     public static final Project ANT_PROJECT = new Project();
 
@@ -53,7 +56,7 @@ public final class InstallerSupport {
     protected static void createDirectory(final Path dir) throws BuildException{
         if (!Files.exists(dir)) {
             try {
-                Files.createDirectory(dir);
+                Files.createDirectories(dir);
                 LOG.debug("Created directory {}", dir);
             } catch (final IOException e) {
                 LOG.error("Could no create {}", dir, e);
@@ -108,7 +111,7 @@ public final class InstallerSupport {
         copy.addFileset(fromSet);
         copy.setProject(ANT_PROJECT);
         copy.execute();
-        LOG.debug("Copied not-previously existing files from {} to {}", from, to);
+        LOG.debug("Copied not-previously-existing files from {} to {}", from, to);
 
     }
 
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/KeyManagement.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/KeyManagement.java
index e97575b..3133372 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/KeyManagement.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/KeyManagement.java
@@ -21,57 +21,82 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
 
+import javax.annotation.Nonnull;
+
 import org.apache.tools.ant.BuildException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.security.BasicKeystoreKeyStrategyTool;
 import net.shibboleth.utilities.java.support.security.SelfSignedCertificateGenerator;
 
 /**
  * Create (if needs be) all the keys needed by an install.
  */
-public class KeyManagement {
+final class KeyManagement extends AbstractInitializableComponent {
 
     /** Log. */
     private final Logger log = LoggerFactory.getLogger(KeyManagement.class);
 
     /** Properties for the job. */
-    private final InstallerProperties installerProps;
+    @Nonnull private final InstallerProperties installerProps;
+
+    /** Current Install. */
+    @Nonnull private final CurrentInstallState currentState;
+    
+    /** Did we create idp-signing.*?*/
+    private boolean createdSigning;
+
+    /** Did we create idp-encryption.*?*/
+    private boolean createdEncryption;
+
+    /** Did we create idp-backchannel.*?*/
+    private boolean createdBackchannel;
+
+    /** Did we create sealer.*?*/
+    private boolean createdSealer;
 
     /** Constructor.
-     * @param props The environment for the work.
+     * @param props The properties to drive the installs. 
+     * @param installState - about where we installing into.
      */
-    public KeyManagement(final InstallerProperties props) {
+    protected KeyManagement(@Nonnull final InstallerProperties props, @Nonnull final CurrentInstallState installState) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(props);
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(installState);
         installerProps = props;
+        currentState = installState;
     }
 
     /** Create any keys that are needed.
      * @throws BuildException if badness occurs
      */
-    public void execute() throws BuildException {
-        generateKey("idp-signing");
-        generateKey("idp-encryption");
+    protected void execute() throws BuildException {
+        createdSigning = generateKey("idp-signing");
+        createdEncryption = generateKey("idp-encryption");
         generateKeyStore();
         generateSealer();
     }
 
     /** Helper method for {@link #manageKeys(InstallerProperties)} to generate a crt and key file.
      * @param fileBase the partial file name
+     * @return true iff the file pair was created
      * @throws BuildException if badness occurrs.
      */
-    private void generateKey(final String fileBase) throws BuildException {
+    private boolean generateKey(final String fileBase) throws BuildException {
         final Path credentials = installerProps.getTargetDir().resolve("credentials");
         final Path key = credentials.resolve(fileBase+".key");
         final Path crt = credentials.resolve(fileBase+".crt");
 
         if (Files.exists(key) && Files.exists(crt)) {
-            if (!installerProps.isIdPPropertiesPresent()) {
+            if (!currentState.isIdPPropertiesPresent()) {
                 log.error("key files {} and {} exist but idp.properties does not", key, crt);
                 throw new BuildException("Invalid key file configuration");
             }
             log.debug("keys files {} and {} exist.  Not generating", key, crt);
-        } else if (installerProps.isIdPPropertiesPresent()) {
+            return false;
+        } else if (currentState.isIdPPropertiesPresent()) {
             log.error("idp.properties exists but key files {} and/or {} do not", key, crt);
             throw new BuildException("Invalid key file configuration");
         } else if (Files.exists(key) || Files.exists(crt)) {
@@ -93,6 +118,8 @@ public class KeyManagement {
                 throw new BuildException("Error Building Self Signed Cert", e);
             }
         }
+        log.debug("... Done");
+        return true;
     }
 
     /** Helper method for {@link #manageKeys(InstallerProperties)} to generate the backchannel keystore.
@@ -104,12 +131,12 @@ public class KeyManagement {
         final Path crt = credentials.resolve("idp-backchannel.crt");
 
         if (Files.exists(keyStore) && Files.exists(crt)) {
-            if (!installerProps.isIdPPropertiesPresent()) {
+            if (!currentState.isIdPPropertiesPresent()) {
                 log.error("Key store files {} and {} exist but idp.properties does not", keyStore, crt);
                 throw new BuildException("Invalid key file configuration");
             }
             log.debug("Keys store files {} and {} exist.  Not generating", keyStore, crt);
-        } else if (installerProps.isIdPPropertiesPresent()) {
+        } else if (currentState.isIdPPropertiesPresent()) {
             log.error("idp.properties exists but key store files {} and/or {} do not", keyStore, crt);
             throw new BuildException("Invalid key file configuration");
         } else if (Files.exists(keyStore) || Files.exists(crt)) {
@@ -131,6 +158,7 @@ public class KeyManagement {
                   log.error("Error building backchannel ketsyore files", e);
                   throw new BuildException("Error Building Backchannel Key Store", e);
               }
+            createdBackchannel = true;
           }
     }
 
@@ -143,12 +171,12 @@ public class KeyManagement {
         final Path versionFile = credentials.resolve("sealer.kver");
 
         if (Files.exists(sealer)  && Files.exists(versionFile)) {
-            if (!installerProps.isIdPPropertiesPresent()) {
+            if (!currentState.isIdPPropertiesPresent()) {
                 log.error("Cookie encryption files {} and {} exist but idp.properties does not", sealer, versionFile);
                 throw new BuildException("Invalid Cookie encryption  file configuration");
             }
             log.debug("Cookie encryption files {} and {} exists.  Not generating.", sealer, versionFile);
-        } else if (installerProps.isIdPPropertiesPresent()) {
+        } else if (currentState.isIdPPropertiesPresent()) {
             log.error("idp.properties exists but cookie encryption files {} do not", sealer, versionFile);
             throw new BuildException("Invalid key file configuration");
         } else if (Files.exists(sealer) || Files.exists(versionFile)) {
@@ -168,6 +196,35 @@ public class KeyManagement {
                 log.error("Error building cookie encryption files", e);
                 throw new BuildException("Error Building Cookie Encryption", e);
             }
+            createdSealer = true;
         }
     }
+
+    /** Did we create idp-signing.*?
+     * @return whether we did
+     */
+    public boolean isCreatedSigning() {
+        return createdSigning;
+    }
+
+    /** Did we create idp-encryption.*?
+     * @return whether we did
+     */
+    public boolean isCreatedEncryption() {
+        return createdEncryption;
+    }
+
+    /** Did we create idp-backchannel.*?
+     * @return whether we did
+     */
+    public boolean isCreatedBackchannel() {
+        return createdBackchannel;
+    }
+
+    /** Did we create sealer.*?
+     * @return whether we did
+     */
+    public boolean isCreatedSealer() {
+        return createdSealer;
+    }
 }
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/PropertiesWithComments.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/PropertiesWithComments.java
index 84353fc..e5b036d 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/PropertiesWithComments.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/PropertiesWithComments.java
@@ -40,7 +40,7 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
  * A package which is similar to Properties, but allows comments to be preserved. We use the Properties package to parse
  * the non-comment lines.
  */
-public class PropertiesWithComments {
+public final class PropertiesWithComments {
 
     /**
      * The contents.
@@ -54,7 +54,7 @@ public class PropertiesWithComments {
     private Map<String, CommentedProperty> properties;
 
     /** Name Replacement info. */
-    private final Properties nameReplacement = new Properties();
+    private final Properties nameReplacement;
 
     /** Have we loaded data?.
      *
@@ -62,6 +62,27 @@ public class PropertiesWithComments {
      * */
     private boolean loadedData;
 
+    /** Legacy Constructor. */
+    public PropertiesWithComments() {
+        nameReplacement = new Properties();
+    }
+
+    /** Constructor.
+     * @param replacements what to set.
+     */
+    public PropertiesWithComments(final Properties replacements) {
+        nameReplacement = replacements;
+    }
+
+    /** Constructor.
+     * @param input what to set.
+     * @throws IOException id the stream could not be loaded
+     */
+    public PropertiesWithComments(final InputStream input) throws IOException {
+        nameReplacement = new Properties();
+        nameReplacement.load(input);
+    }
+
     /**
      * Add a property, either as a key/value pair or as a key/comment pair.
      * 
@@ -191,6 +212,18 @@ public class PropertiesWithComments {
         }
     }
 
+    /** Perform a mass replacement from the supplied {@link Properties}.
+     * @param replacements what to replace.
+     */
+    public void replaceProperties(final Properties replacements) {
+        for (final Object propName:replacements.keySet()) {
+            if (propName instanceof String) {
+                final String name = (String) propName;
+                replaceProperty(name, replacements.getProperty(name));
+            }
+        }
+    }
+
     /**
      * Replace the supplied property or stuff it at the bottom of the list.
      * 
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V4Install.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V4Install.java
new file mode 100644
index 0000000..5a8350a
--- /dev/null
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/impl/V4Install.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.installer.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import javax.annotation.Nonnull;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.Copy;
+import org.joda.time.Instant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.Version;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
+/** Code to do most of the V4 Install.
+ */
+public class V4Install extends AbstractInitializableComponent {
+
+    /** Log. */
+    private final Logger log = LoggerFactory.getLogger(V4Install.class);
+
+    /** Installer Properties. */
+    @Nonnull private final InstallerProperties installerProps;
+
+    /** Current Install. */
+    @Nonnull private final CurrentInstallState currentState;
+
+    /** Constructor.
+     * @param props The properties to drive the installs.
+     * @param installState The current install.
+     */
+    public V4Install(@Nonnull final InstallerProperties props, @Nonnull final CurrentInstallState installState) {
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(props);
+        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(installState);
+        installerProps = props;
+        currentState = installState;
+    }
+
+    /** Method to do the work. It assumes that the distribution has been copied.
+     * @throws BuildException if unexpected badness occurs.
+     */
+    public void execute() throws BuildException {
+        handleVersioning();
+        // To keep the UI order the same as the V3 Installer
+        //installerProps.getEntityID();
+        //installerProps.getScope();
+        createUserDirectories();
+        final KeyManagement keys = new KeyManagement(installerProps, currentState);
+        keys.execute();
+        populatePropertyFiles(keys.isCreatedSealer());
+        handleEditWebApp();
+        populateUserDirectories();
+        generateMetadata();
+        reprotect();
+    }
+
+    /** Report the to be installed and (if there is one) current versions. 
+     * Write to be installed version to the dist folder.
+     * @throws BuildException if the write fails
+     */
+    protected void handleVersioning() throws BuildException {
+        final String installedVersion = currentState.getInstalledVersion();
+        String currentVersion = Version.getVersion();
+        if (null == currentVersion) {
+            currentVersion = "4Generic";
+        }
+        if (null == installedVersion) {
+            log.info("New Install.  Version: {}", currentVersion);
+        } else if (currentVersion == installedVersion) {
+            log.info("Reinstall of version {}", currentVersion);
+        } else {
+            log.info("Update from version {} to version {}", installedVersion, currentVersion);
+        }
+        try {
+            final Properties vers = new Properties();
+            vers.setProperty(InstallerSupport.VERSION_NAME, currentVersion);
+            vers.setProperty(InstallerSupport.PREVIOUS_VERSION_NAME, installedVersion==null?"":installedVersion);
+            final OutputStream out = new FileOutputStream(
+                    installerProps.getTargetDir().resolve("dist").resolve(InstallerSupport.VERSION_NAME).toFile());
+            vers.store(out, "Version file written at " + Instant.now());
+        } catch (final IOException e) {
+            throw new BuildException("Couldn't write versiining information", e);
+        }
+    }
+
+    /** Create (if they do not exist) the user editable folders, suitable for
+     * later population during update or install.
+     * @throws BuildException if badness occurs
+     */
+    protected void createUserDirectories() throws BuildException {
+        final Path target = installerProps.getTargetDir();
+        InstallerSupport.createDirectory(target.resolve("conf"));
+        InstallerSupport.createDirectory(target.resolve("credentials"));
+        InstallerSupport.createDirectory(target.resolve("flows"));
+        InstallerSupport.createDirectory(target.resolve("logs"));
+        InstallerSupport.createDirectory(target.resolve("messages"));
+        InstallerSupport.createDirectory(target.resolve("metadata"));
+        InstallerSupport.createDirectory(target.resolve("views"));
+        InstallerSupport.createDirectory(target.resolve("war"));
+    }
+    
+    /** Create the properties we need to replace when we merge idp.properties.
+     * @param sealerCreated have we just created a sealer
+     * @return what we need to replace
+     */
+    private Properties getIdPReplacements(final boolean sealerCreated) {
+        final Properties result = new Properties();
+        if (sealerCreated) {
+            result.setProperty("idp.sealer.storePassword", installerProps.getSealerPassword());
+            result.setProperty("idp.sealer.keyPassword", installerProps.getSealerPassword());
+        }
+        result.setProperty("idp.entityID", installerProps.getEntityID());
+        result.setProperty("idp.scope", installerProps.getScope());
+        return result;
+    }
+
+    /** Create (if they do not exist) propertyFiles. (idp.properties, ldap.properties).
+     * This *MUST* happen before {@link #populateUserDirectories(InstallerProperties)} or it will not be effective.
+     * Note that in V3 serice.properties and nameid.properties but we do not any more.
+     * @param sealerCreated have we just created a sealer
+     * @throws BuildException if badness occurs
+     */
+    // CheckStyle: CyclomaticComplexity OFF
+    protected void populatePropertyFiles(final boolean sealerCreated) throws BuildException {
+        final Path conf = installerProps.getTargetDir().resolve("conf");
+        final Path dstConf = installerProps.getTargetDir().resolve("dist").resolve("conf");
+        if (!currentState.isIdPPropertiesPresent()) {
+            // We have to populate it
+            try {
+                final Path target = conf.resolve("idp.properties");
+                if (Files.exists(target)) {
+                    throw new BuildException("Internal error - idp.properties");
+                }
+                final File mergeFile = installerProps.getIdPMergePropertiesFile();
+                final Path source = dstConf.resolve("idp.properties");
+                if (!Files.exists(source)) {
+                    throw new BuildException("missing idp.properties in dist");
+                }
+                final PropertiesWithComments propertiesToReWrite = new PropertiesWithComments();
+                final Properties replacements;
+                if (mergeFile != null) {
+                    log.debug("Creating {} from {} and {}", target, source, mergeFile);
+                    replacements = new Properties();
+                    replacements.load(new FileInputStream(mergeFile));
+                } else {
+                    replacements = getIdPReplacements(sealerCreated);
+                    log.debug("Creating {} from {} and {}", target, source, replacements);
+                }
+                propertiesToReWrite.load(new FileInputStream(source.toFile()));
+                propertiesToReWrite.replaceProperties(replacements);
+                propertiesToReWrite.store(new FileOutputStream(target.toFile()));
+            } catch (final IOException e) {
+                throw new BuildException("Failed to generate idp.properties", e);
+            }
+        }
+
+        final File ldapMergeFile = installerProps.getLDAPMergePropertiesFile();
+        if (ldapMergeFile != null && !currentState.isLDAPPropertiesPresent() ) {
+            try {
+                final Path target = conf.resolve("ldap.properties");
+                if (Files.exists(target)) {
+                    throw new BuildException("Internal error - ldap.properties");
+                }
+                final Path source = dstConf.resolve("ldap.properties");
+                if (!Files.exists(source)) {
+                    throw new BuildException("missing ldap.properties in dist");
+                }
+                log.debug("Creating {} from {} and {}", target, source, ldapMergeFile);
+                final PropertiesWithComments propertiesToReWrite = new PropertiesWithComments();
+                final Properties replacements = new Properties();
+                replacements.load(new FileInputStream(ldapMergeFile));
+                propertiesToReWrite.load(new FileInputStream(source.toFile()));
+                propertiesToReWrite.replaceProperties(replacements);
+                propertiesToReWrite.store(new FileOutputStream(target.toFile()));
+            } catch (final IOException e) {
+                throw new BuildException("Failed to generate ldap.properties", e);
+            }
+        }
+    }
+    // CheckStyle: CyclomaticComplexity ON
+
+    /** Create and populate (if it does not exist) edit-webapp.
+     * @throws BuildException if badness occurs
+     */
+    protected void handleEditWebApp() throws BuildException {
+        final Path editWebApp = installerProps.getTargetDir().resolve("edit-webapp");
+        if (Files.exists(editWebApp)) {
+            return;
+        }
+        InstallerSupport.createDirectory(editWebApp);
+        final Path css = editWebApp.resolve("css");
+        InstallerSupport.createDirectory(css);
+        final Path images = editWebApp.resolve("images");
+        InstallerSupport.createDirectory(images);
+        InstallerSupport.createDirectory(editWebApp.resolve("WEB-INF"));
+        InstallerSupport.createDirectory(editWebApp.resolve("WEB-INF").resolve("lib"));
+        InstallerSupport.createDirectory(editWebApp.resolve("WEB-INF").resolve("classes"));
+        final Path distEditWebApp =  installerProps.getTargetDir().resolve("dist").resolve("webapp");
+        final Copy cssCopy = InstallerSupport.getCopyTask(distEditWebApp.resolve("css"), css);
+        cssCopy.setFailOnError(false);
+        cssCopy.execute();
+        final Copy imagesCopy = InstallerSupport.getCopyTask(distEditWebApp.resolve("images"), images);
+        imagesCopy.setFailOnError(false);
+        imagesCopy.execute();       
+    }
+
+    /** Create and populate (if they not exist) the "user visible" folders.
+     * (conf, flows, messages, views, logs)
+     * @throws BuildException if badness occurs
+     */
+    protected void populateUserDirectories() throws BuildException {
+        final Path targetBase = installerProps.getTargetDir();
+        final Path distBase = targetBase.resolve("dist");
+        InstallerSupport.copyDirIfNotPresent(distBase.resolve("conf"), targetBase.resolve("conf"));
+        InstallerSupport.copyDirIfNotPresent(distBase.resolve("flows"), targetBase.resolve("flows"));
+        InstallerSupport.copyDirIfNotPresent(distBase.resolve("views"), targetBase.resolve("views"));
+        InstallerSupport.copyDirIfNotPresent(distBase.resolve("messages"), targetBase.resolve("messages"));
+        InstallerSupport.createDirectory(targetBase.resolve("logs"));
+    }
+    
+    /** Create and populate (if it does not exist) the "metadata/idp-metadata.xml" file.
+     * @throws BuildException if badness occurs
+     */
+    protected void generateMetadata() throws BuildException {
+        final Path parentDir = installerProps.getTargetDir().resolve("metadata");
+        final Path metadataFile = parentDir.resolve("idp-metadata");
+        if (Files.exists(metadataFile)) {
+            return;
+        }
+        log.warn("Metadata Implementation still pending");        
+    }
+
+    /** Set the protection on the files.
+     * @throws BuildException if badness occurs
+     */
+    protected void reprotect() throws BuildException {
+        log.warn("Reprotect Implementation still pending");
+    }
+
+}
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/installer/Test.java b/idp-installer/src/test/java/net/shibboleth/idp/installer/impl/Test.java
similarity index 100%
rename from idp-installer/src/test/java/net/shibboleth/idp/installer/Test.java
rename to idp-installer/src/test/java/net/shibboleth/idp/installer/impl/Test.java

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list