[java-idp-plugin-metadatagen] branch dev/JMETAGEN-5 updated: Initial revamp of plugin around Velocity.

Scott Cantor cantor.2 at osu.edu
Wed Jul 5 16:34:24 UTC 2023


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

scantor pushed a commit to branch dev/JMETAGEN-5
in repository java-idp-plugin-metadatagen.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-metadatagen.git;a=commit;h=850c25c5c235c978a701eae1487c76e060c8bb4a

The following commit(s) were added to refs/heads/dev/JMETAGEN-5 by this push:
     new 850c25c  Initial revamp of plugin around Velocity.
850c25c is described below

commit 850c25c5c235c978a701eae1487c76e060c8bb4a
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Jul 5 12:34:21 2023 -0400

    Initial revamp of plugin around Velocity.
---
 metadatagen-api/.checkstyle                        |  12 -
 metadatagen-api/pom.xml                            |  51 --
 .../shibboleth/idp/plugin/metadatagen/Version.java |  48 --
 metadatagen-impl/.checkstyle                       |   2 +-
 metadatagen-impl/pom.xml                           |  39 +-
 .../plugin/metadatagen/impl/MetadataGenCLI.java    | 818 +++++++++++----------
 .../impl/MetadataGenCommandLineArguments.java      | 459 +++++++++---
 .../metadatagen-templates/EntityDescriptor.vm      |   6 +
 .../idp/plugin/metadatagen/conf/velocity.xml       |  53 ++
 .../idp/plugin/metadatagen/impl/plugin.properties  |   2 +-
 .../idp/plugin/metadatagen/metadatagen.xml         |  23 -
 .../plugin/metadatagen/impl/MetadataGenTest.java   |  48 +-
 .../src/test/resources/logback-test.xml            |   6 +-
 pom.xml                                            |   6 -
 .../checkstyle/checkstyle.xml                      |   0
 15 files changed, 891 insertions(+), 682 deletions(-)

diff --git a/metadatagen-api/.checkstyle b/metadatagen-api/.checkstyle
deleted file mode 100644
index 42d2507..0000000
--- a/metadatagen-api/.checkstyle
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
-  <local-check-config name="Shibboleth Checkstyle" location="/idp-plugin-metadatagen-parent/checkstyle.xml" type="project" description="">
-    <additional-data name="cache-props-file-location" value="null_1312636288299_cache.properties"/>
-    <additional-data name="cache-file-location" value="null_1312636288299_cache.xml"/>
-    <additional-data name="protect-config-file" value="false"/>
-  </local-check-config>
-  <fileset name="all" enabled="true" check-config-name="Shibboleth Checkstyle" local="true">
-    <file-match-pattern match-pattern="src/main/java/.*\.java$." include-pattern="true"/>
-  </fileset>
-</fileset-config>
diff --git a/metadatagen-api/pom.xml b/metadatagen-api/pom.xml
deleted file mode 100644
index 7212b38..0000000
--- a/metadatagen-api/pom.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>net.shibboleth.idp.plugin.metadatagen</groupId>
-        <artifactId>idp-plugin-metadatagen-parent</artifactId>
-        <version>2.0.0-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>idp-plugin-metadatagen-api</artifactId>
-    <packaging>jar</packaging>
-    <name>Shibboleth IdP :: Plugins :: metadatagen API</name>
-    <description>IdP metadatagen plugin API.</description>
-
-    <properties>
-        <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
-        <automatic.module.name>net.shibboleth.idp.plugin.metadatagen.api</automatic.module.name>
-    </properties>
-
-    <dependencies>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <index>true</index>
-                        <manifestEntries>
-                            <Main-Class>net.shibboleth.idp.plugin.metadatagen.Version</Main-Class>
-                        </manifestEntries>
-                        <manifestSections>
-                            <manifestSection>
-                                <name>org/example/com/metadatagen/</name>
-                                <manifestEntries>
-                                    <Implementation-Title>${project.artifactId}</Implementation-Title>
-                                    <Implementation-Version>${project.version}</Implementation-Version>
-                                    <Implementation-Vendor>shibboleth.net</Implementation-Vendor>
-                                </manifestEntries>
-                            </manifestSection>
-                        </manifestSections>
-                    </archive>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-</project>
diff --git a/metadatagen-api/src/main/java/net/shibboleth/idp/plugin/metadatagen/Version.java b/metadatagen-api/src/main/java/net/shibboleth/idp/plugin/metadatagen/Version.java
deleted file mode 100644
index 2350b93..0000000
--- a/metadatagen-api/src/main/java/net/shibboleth/idp/plugin/metadatagen/Version.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.plugin.metadatagen;
-
-import javax.annotation.Nullable;
-
-/** Class for getting and printing the version of the IdP. */
-public final class Version {
-
-    /** IdP version. */
-    @Nullable private static final String VERSION = Version.class.getPackage().getImplementationVersion();
-
-    /** Constructor. */
-    private Version() {
-    }
-
-    /**
-     * Main entry point to program.
-     * 
-     * @param args command line arguments
-     */
-    public static void main(final String[] args) {
-        System.out.println(VERSION);
-    }
-
-    /**
-     * Get the version of the IdP.
-     * 
-     * @return version of the IdP
-     */
-    @Nullable public static String getVersion() {
-        return VERSION;
-    }
-}
\ No newline at end of file
diff --git a/metadatagen-impl/.checkstyle b/metadatagen-impl/.checkstyle
index 42d2507..573e4e0 100644
--- a/metadatagen-impl/.checkstyle
+++ b/metadatagen-impl/.checkstyle
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
-  <local-check-config name="Shibboleth Checkstyle" location="/idp-plugin-metadatagen-parent/checkstyle.xml" type="project" description="">
+  <local-check-config name="Shibboleth Checkstyle" location="/java-idp-plugin-metadatagen/resources/checkstyle/checkstyle.xml" type="project" description="">
     <additional-data name="cache-props-file-location" value="null_1312636288299_cache.properties"/>
     <additional-data name="cache-file-location" value="null_1312636288299_cache.xml"/>
     <additional-data name="protect-config-file" value="false"/>
diff --git a/metadatagen-impl/pom.xml b/metadatagen-impl/pom.xml
index c5d3207..b288b0a 100644
--- a/metadatagen-impl/pom.xml
+++ b/metadatagen-impl/pom.xml
@@ -19,11 +19,6 @@
     </properties>
     
     <dependencies>
-         <!-- compile time intra project dependencies -->
-        <dependency>
-            <groupId>net.shibboleth.idp.plugin.metadatagen</groupId>
-            <artifactId>idp-plugin-metadatagen-api</artifactId>
-        </dependency>
         <!-- Service API and Plugin Description dependency -->
         <dependency>
             <groupId>${idp.groupId}</groupId>
@@ -47,6 +42,22 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-saml-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-security-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${opensaml.groupId}</groupId>
+            <artifactId>opensaml-xmlsec-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>${shib-shared.groupId}</groupId>
             <artifactId>shib-cli</artifactId>
@@ -57,7 +68,25 @@
             <artifactId>shib-support</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>${shib-shared.groupId}</groupId>
+            <artifactId>shib-velocity</artifactId>
+            <scope>provided</scope>
+        </dependency>
     
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Test dependencies. -->
+        <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-conf-impl</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
 
diff --git a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java
index bdc718b..b5aa173 100644
--- a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java
+++ b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCLI.java
@@ -22,15 +22,24 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
 import org.opensaml.core.xml.LangBearing;
 import org.opensaml.saml.common.xml.SAMLConstants;
 import org.opensaml.saml.ext.reqattr.RequestedAttributes;
@@ -49,47 +58,57 @@ import org.opensaml.saml.saml2.metadata.KeyDescriptor;
 import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
 import org.opensaml.saml.saml2.metadata.SingleLogoutService;
 import org.opensaml.saml.saml2.metadata.SingleSignOnService;
+import org.opensaml.security.x509.X509Support;
 import org.opensaml.xmlsec.signature.KeyInfo;
-import org.opensaml.xmlsec.signature.X509Certificate;
 import org.opensaml.xmlsec.signature.X509Data;
 import org.opensaml.xmlsec.signature.support.SignatureConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.core.env.Environment;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.core.io.Resource;
 
-import net.shibboleth.idp.Version;
 import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLine;
 import net.shibboleth.idp.saml.xmlobject.ExtensionsConstants;
 import net.shibboleth.idp.saml.xmlobject.Scope;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.cli.AbstractCommandLine;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.velocity.Template;
 import net.shibboleth.shared.xml.DOMTypeSupport;
 import net.shibboleth.shared.xml.XMLConstants;
 
 /**
- * Command Line to generate Metadata.
+ * Command Line to generate SAML metadata.
  */
 public final class MetadataGenCLI extends AbstractIdPHomeAwareCommandLine<MetadataGenCommandLineArguments> {
 
+    /** VelocityEngine bean name. */
+    @Nonnull @NotEmpty private static String VELOCITY_ENGINE_BEAN_NAME = "shibboleth.VelocityEngine";
+    
     /** Class logger. */
     @Nullable private Logger log;
 
-    /** Certificate and other property driven data. */
-    private MetadataGenParameters parameters;
-
     /** Where we are outputting to? */
     private PrintWriter output;
 
     /** The processed arguments. */
     private MetadataGenCommandLineArguments args;
     
-    /** The DnsName (cached because we need it often). */
-    @Nullable private String dnsName;
+    /** Velocity engine for output generation. */
+    private VelocityEngine velocityEngine;
+    
+    /** Constructor. */
+    public MetadataGenCLI() {
+        setCaseSensitiveOptions(false);
+    }
     
     /** {@inheritDoc} */
     @Override
@@ -109,412 +128,408 @@ public final class MetadataGenCLI extends AbstractIdPHomeAwareCommandLine<Metada
 
     /** {@inheritDoc} */
     @Nonnull protected String getVersion() {
-        return Version.getVersion();
-    }
-
-    /** {@inheritDoc} */
-    @Nonnull protected List<Resource> getAdditionalSpringResources() {
-        return CollectionSupport.singletonList(new ClassPathResource("net/shibboleth/idp/plugin/metadatagen/metadatagen.xml"));
+        return getClass().getPackage().getImplementationVersion();
     }
 
-    /**
-     * Write out any <KeyDescriptor>Elements.
-     * @param outputBackChannel  Do we output the back channel certificates?
-     */
-    private void outputKeyDescriptors(final boolean outputBackChannel) {
-        final List<List<String>> signing = new ArrayList<>(2);
-        if (outputBackChannel) {
-            final List<String> backCert = parameters.getBackchannelCert();
-            if (backCert != null && !backCert.isEmpty()) {
-                output.format("        <!--  First signing certificate is BackChannel, the Second is FrontChannel -->\n");
-                signing.add(backCert);
-            }
-        }
-        final List<String> signCert = parameters.getSigningCert();
-        if (signCert != null && !signCert.isEmpty()) {
-            signing.add(signCert);
-        }
-        outputKeyDescriptors(signing, "signing");
-        outputKeyDescriptors(Collections.singletonList(parameters.getEncryptionCert()), "encryption");
-        output.format("\n");
-    }
+//    /**
+//     * Write out any <KeyDescriptor>Elements.
+//     * @param outputBackChannel  Do we output the back channel certificates?
+//     */
+//    private void outputKeyDescriptors(final boolean outputBackChannel) {
+//        final List<List<String>> signing = new ArrayList<>(2);
+//        if (outputBackChannel) {
+//            final List<String> backCert = parameters.getBackchannelCert();
+//            if (backCert != null && !backCert.isEmpty()) {
+//                output.format("        <!--  First signing certificate is BackChannel, the Second is FrontChannel -->\n");
+//                signing.add(backCert);
+//            }
+//        }
+//        final List<String> signCert = parameters.getSigningCert();
+//        if (signCert != null && !signCert.isEmpty()) {
+//            signing.add(signCert);
+//        }
+//        outputKeyDescriptors(signing, "signing");
+//        outputKeyDescriptors(Collections.singletonList(parameters.getEncryptionCert()), "encryption");
+//        output.format("\n");
+//    }
+//
+//    /**
+//     * Write out <KeyDescriptor>Elements. of a specific type
+//     *
+//     * @param certs the certificates
+//     * @param use the type - signing or encryption
+//     */
+//    private void outputKeyDescriptors(@Nullable final List<List<String>> certs, @Nonnull @NotEmpty final String use) {
+//
+//        if (null == certs || certs.isEmpty()) {
+//            return;
+//        }
+//        for (final List<String> cert : certs) {
+//            output.format("        <%s use=\"%s\">\n",KeyDescriptor.DEFAULT_ELEMENT_LOCAL_NAME, use);
+//            output.format("          <%s:%s>\n", SignatureConstants.XMLSIG_PREFIX, KeyInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+//            output.format("            <%s:%s>\n",
+//                    SignatureConstants.XMLSIG_PREFIX, X509Data.DEFAULT_ELEMENT_LOCAL_NAME);
+//            output.format("              <%s:%s>\n",
+//                    SignatureConstants.XMLSIG_PREFIX, X509Certificate.DEFAULT_ELEMENT_LOCAL_NAME);
+//            output.format("%s\n",String.join("\n", cert));
+//            output.format("              </%s:%s>\n",
+//                    SignatureConstants.XMLSIG_PREFIX, X509Certificate.DEFAULT_ELEMENT_LOCAL_NAME);
+//            output.format("            </%s:%s>\n",
+//                    SignatureConstants.XMLSIG_PREFIX, X509Data.DEFAULT_ELEMENT_LOCAL_NAME);
+//            output.format("          </%s:%s>\n",SignatureConstants.XMLSIG_PREFIX, KeyInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+//            output.format("        </%s>\n\n",KeyDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+//        }
+//    }
+//
+//    /** Output the MDUI for one language.
+//     * @param lang the language to emit
+//     *
+//     */
+//    private void outputMDUI(final String lang) {
+//        final Environment env = getApplicationContext().getEnvironment();
+//        final String displayName = env.getProperty(MetadataGenCommandLineArguments.MDUI_DISPLAY_NAME_PREFIX+lang);
+//        if (displayName != null) {
+//            output.format("            <%s:%s %s:%s=\"%s\">%s</%s:%s>\n",
+//                    SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+//                    lang, displayName,
+//                    SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
+//        }
+//        final String description = env.getProperty(MetadataGenCommandLineArguments.MDUI_DESCRIPTION_PREFIX+lang);
+//        if (description != null) {
+//            output.format("            <%s:%s %s:%s=\"%s\">%s</%s:%s>\n",
+//                    SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+//                    lang, description,
+//                    SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME);
+//        }
+//    }
+//
+//    /** Output Scope and MDUI.
+//     * @param includeMDUI do we output the MDUI
+//     */
+//    private void outputExtensions(final boolean includeMDUI) {
+//        final Environment env = getApplicationContext().getEnvironment();
+//        final String scope = StringSupport.trimOrNull(env.getProperty("idp.scope"));
+//        if (!includeMDUI && scope == null) {
+//                return;
+//        }
+//        output.format("        <%s>\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
+//        if (scope != null) {
+//            output.format("          <%s:%s regexp=\"false\">%s</%s:%s>\n",
+//                    ExtensionsConstants.SHIB_MDEXT10_PREFIX, Scope.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    scope,
+//                    ExtensionsConstants.SHIB_MDEXT10_PREFIX, Scope.DEFAULT_ELEMENT_LOCAL_NAME);
+//        }
+//
+//        if (includeMDUI) {
+//            output.format("          <%s:%s>\n",
+//                    SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+//            final String mduiLangs = env.getProperty(MetadataGenCommandLineArguments.MDUI_LANGS_PROPERTY);
+//            if (mduiLangs == null) {
+//                output.format("\n<!--\n          Fill in the details for your IdP here\n\n");
+//                output.format("            <%s:%s %s:%s=\"en\">A Name for the IdP</%s:%s>\n",
+//                        SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME,
+//                        XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+//                        SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
+//                output.format("            <%s:%s %s:%s=\"en\">A Description for the IdP</%s:%s>\n",
+//                        SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME,
+//                        XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
+//                        SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME);
+//            } else {
+//                for (final String lang:mduiLangs.split(" ")) {
+//                    outputMDUI(lang);
+//                }
+//            }
+//            final String logoY = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_HEIGHT, "80");
+//            final String logoX = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_WIDTH, "80");
+//            final String logoPath = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_PATH, "/path/to/logo");
+//            output.format("            <%s:%s height=\"%s\" width=\"%s\">https://%s%s</%s:%s>\n",
+//                    SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    logoY, logoX,  getDnsName(), logoPath,
+//                    SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME);
+//            if (mduiLangs == null) {
+//                output.format("-->\n\n");
+//            }
+//            output.format("          </%s:%s>\n", SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
+//        }
+//        output.format("\n        </%s>\n\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
+//    }
+//
+//    /** Output Logout end points. */
+//    private void outputLogoutEndpoints() {
+//        output.format("        <%s Binding=\"%s\""
+//                + " Location=\"https://%s/idp/profile/SAML2/Redirect/SLO\"/>\n",
+//                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_REDIRECT_BINDING_URI,
+//                getDnsName());
+//        output.format("        <%s Binding=\"%s\""
+//                + " Location=\"https://%s/idp/profile/SAML2/POST/SLO\"/>\n",
+//                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_POST_BINDING_URI,
+//                getDnsName());
+//        output.format("        <%s Binding=\"%s\""
+//                + " Location=\"https://%s/idp/profile/SAML2/POST/SLO-SimpleSign\"/>\n",
+//                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
+//                getDnsName());
+//
+//        final List<String> backCert = parameters.getBackchannelCert();
+//        if (backCert != null && !backCert.isEmpty()) {
+//            output.format("        <%s Binding=\"%s\""
+//                    + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/SLO\"/>\n",
+//                    SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    SAMLConstants.SAML2_SOAP11_BINDING_URI,
+//                    getDnsName());
+//        }
+//    }
+//
+//    /** Output Artifact Endpoints. */
+//    private void outputArtifactEndpoints() {
+//        if (args.isSaml1()) {
+//            output.format("        <%s Binding=\"%s\""
+//                    + " Location=\"https://%s:8443/idp/profile/SAML1/SOAP/ArtifactResolution\" index=\"1\"/>\n",
+//                    ArtifactResolutionService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    SAMLConstants.SAML1_SOAP11_BINDING_URI,
+//                    getDnsName());
+//        }
+//        output.format("        <%s Binding=\"%s\""
+//                + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/ArtifactResolution\" index=\"2\"/>\n",
+//                ArtifactResolutionService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_SOAP11_BINDING_URI,
+//                getDnsName());
+//    }
+//
+//    /** Output SSO Endpoints. */
+//    private void outputSSOEndpoints() {
+//        if (args.isSaml1()) {
+//            output.format("        <%s Binding=\"%s\""
+//                    + " Location=\"https://%s/idp/profile/Shibboleth/SSO\"/>\n",
+//                    SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    "urn:mace:shibboleth:1.0:profiles:AuthnRequest",
+//                    getDnsName());
+//
+//        }
+//        output.format("        <%s Binding=\"%s\""
+//                + " %s:%s=\"true\""
+//                + " Location=\"https://%s/idp/profile/SAML2/POST/SSO\"/>\n",
+//                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_POST_BINDING_URI,
+//                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
+//                getDnsName());
+//        output.format("        <%s Binding=\"%s\""
+//                + " %s:%s=\"true\""
+//                + " Location=\"https://%s/idp/profile/SAML2/POST-SimpleSign/SSO\"/>\n",
+//                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
+//                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
+//                getDnsName());
+//        output.format("        <%s Binding=\"%s\""
+//                + " %s:%s=\"true\""
+//                + " Location=\"https://%s/idp/profile/SAML2/Redirect/SSO\"/>\n",
+//                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_REDIRECT_BINDING_URI,
+//                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
+//                getDnsName());
+//    }
+//
+//    /**
+//     * Write the <IDPSSODescriptor>.
+//     */
+//    private void outputIDPSSO() {
+//        final List<String> protocols = new ArrayList<>(4);
+//        if (!args.isSaml1() && !args.isSaml2()) {
+//            return;
+//        }
+//        if (args.isSaml1()) {
+//            protocols.add(SAMLConstants.SAML20P_NS);
+//        }
+//        if (args.isSaml2()) {
+//            protocols.add(SAMLConstants.SAML20P_NS);
+//            protocols.add(SAMLConstants.SAML11P_NS);
+//            protocols.add("urn:mace:shibboleth:1.0");
+//        }
+//        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
+//                IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+//                String.join(" ", protocols));
+//
+//        outputExtensions(true);
+//
+//        outputKeyDescriptors(true);
+//
+//        if (args.isArtifact()) {
+//            outputArtifactEndpoints();
+//        }
+//
+//        if (args.isLogout()) {
+//            outputLogoutEndpoints();
+//        }
+//
+//        outputSSOEndpoints();
+//
+//        output.format("    </%s>\n", IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+//    }
+//
+//    /**
+//     * Write the <AttributeAuthorityDescriptor>.*/
+//    private void outputAtttributeAuthorityDescriptor() {
+//        final List<String> protocols;
+//
+//        if (args.isSaml1()) {
+//            if (args.isAttributeFetch()) {
+//                // Both
+//                protocols = Arrays.asList(SAMLConstants.SAML20P_NS, SAMLConstants.SAML11P_NS);
+//            } else {
+//                // SAML1 only
+//                protocols = Collections.singletonList(SAMLConstants.SAML11P_NS);
+//            }
+//        } else if (args.isAttributeFetch()) {
+//            // SAML2 only
+//            protocols = Collections.singletonList(SAMLConstants.SAML20P_NS);
+//        } else {
+//            // Neither
+//            return;
+//        }
+//
+//        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
+//                AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+//                String.join(" ", protocols));
+//
+//        outputExtensions(false);
+//        outputKeyDescriptors(true);
+//        if (args.isSaml1()) {
+//            output.format("        <%s Binding=\"%s\""
+//                    + " Location=\"https://%s:8443/idp/profile/SAML1/SOAP/AttributeQuery\"/>\n",
+//                    AttributeService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    SAMLConstants.SAML1_SOAP11_BINDING_URI,
+//                    getDnsName());
+//        }
+//        if (args.isAttributeFetch()) {
+//            output.format("        <%s Binding=\"%s\""
+//                    + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/AttributeQuery\"/>\n",
+//                    AttributeService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                    SAMLConstants.SAML2_SOAP11_BINDING_URI,
+//                    getDnsName());
+//        }
+//        output.format("    </%s>\n", AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+//    }
+//
+//    /**
+//     * Write the <SPSSODescriptor>.
+//     */
+//    private void outputSPSSO() {
+//        if (!args.isSamlSP()) {
+//            return;
+//        }
+//        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
+//                SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML20P_NS);
+//
+//        outputKeyDescriptors(false);
+//
+//        output.format("        <%s Binding=\"%s\""
+//                + " Location=\"https://%s/idp/profile/Authn/SAML2/POST/SSO\" index=\"0\"/>\n",
+//                AssertionConsumerService.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML2_POST_BINDING_URI,
+//                getDnsName());
+//
+//        output.format("    </%s>\n", SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+//    }
+//
+//
+//    /** Output the metadata.
+//     * @return true iff this worked.
+//     */
+//    private int outputMetadata() {
+//        output.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+//        output.format("   <!--\n");
+//        output.format("     This is example metadata only. Do *NOT* supply it as is without review,\n");
+//        output.format("     and do *NOT* provide it in real time to your partners.\n");
+//        output.format("     This metadata is not dynamic - run metadatagen again to recreate.\n");
+//        output.format("     Created: %s\n   -->\n", Instant.now().toString());
+//        output.format("<%s  xmlns=\"%s\" xmlns:%s=\"%s\"\n", EntityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
+//                SAMLConstants.SAML20MD_NS,
+//                SignatureConstants.XMLSIG_PREFIX, SignatureConstants.XMLSIG_NS);
+//        output.format("      xmlns:%s=\"%s\" xmlns:%s=\"%s\"\n",
+//                ExtensionsConstants.SHIB_MDEXT10_PREFIX, ExtensionsConstants.SHIB_MDEXT10_NS,
+//                XMLConstants.XML_PREFIX, XMLConstants.XML_NS);
+//        output.format("      xmlns:%s=\"%s\" xmlns:%s=\"%s\"\n",
+//                SAMLConstants.SAML20MDUI_PREFIX, SAMLConstants.SAML20MDUI_NS,
+//                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, SAMLConstants.SAML20PREQ_ATTR_NS);
+//        output.format(" validUntil=\"%s\" entityID=\"%s\">\n\n",
+//                DOMTypeSupport.instantToString(Instant.now()), 
+//                getApplicationContext().getEnvironment().getProperty("idp.entityID", "idp.example.org"));
+//        outputIDPSSO();
+//        outputAtttributeAuthorityDescriptor();
+//        outputSPSSO();
+//        output.format("</EntityDescriptor>\n");
+//        output.flush();
+//        output.close();
+//        return RC_OK;
+//    }
 
+    
+    
     /**
-     * Write out <KeyDescriptor>Elements. of a specific type
-     *
-     * @param certs the certificates
-     * @param use the type - signing or encryption
-     */
-    private void outputKeyDescriptors(@Nullable final List<List<String>> certs, @Nonnull @NotEmpty final String use) {
-
-        if (null == certs || certs.isEmpty()) {
-            return;
-        }
-        for (final List<String> cert : certs) {
-            output.format("        <%s use=\"%s\">\n",KeyDescriptor.DEFAULT_ELEMENT_LOCAL_NAME, use);
-            output.format("          <%s:%s>\n", SignatureConstants.XMLSIG_PREFIX, KeyInfo.DEFAULT_ELEMENT_LOCAL_NAME);
-            output.format("            <%s:%s>\n",
-                    SignatureConstants.XMLSIG_PREFIX, X509Data.DEFAULT_ELEMENT_LOCAL_NAME);
-            output.format("              <%s:%s>\n",
-                    SignatureConstants.XMLSIG_PREFIX, X509Certificate.DEFAULT_ELEMENT_LOCAL_NAME);
-            output.format("%s\n",String.join("\n", cert));
-            output.format("              </%s:%s>\n",
-                    SignatureConstants.XMLSIG_PREFIX, X509Certificate.DEFAULT_ELEMENT_LOCAL_NAME);
-            output.format("            </%s:%s>\n",
-                    SignatureConstants.XMLSIG_PREFIX, X509Data.DEFAULT_ELEMENT_LOCAL_NAME);
-            output.format("          </%s:%s>\n",SignatureConstants.XMLSIG_PREFIX, KeyInfo.DEFAULT_ELEMENT_LOCAL_NAME);
-            output.format("        </%s>\n\n",KeyDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
-        }
-    }
-
-    /** Output the MDUI for one language.
-     * @param lang the language to emit
-     *
-     */
-    private void outputMDUI(final String lang) {
-        final Environment env = getApplicationContext().getEnvironment();
-        final String displayName = env.getProperty(MetadataGenCommandLineArguments.MDUI_DISPLAY_NAME_PREFIX+lang);
-        if (displayName != null) {
-            output.format("            <%s:%s %s:%s=\"%s\">%s</%s:%s>\n",
-                    SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME,
-                    XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
-                    lang, displayName,
-                    SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
-        }
-        final String description = env.getProperty(MetadataGenCommandLineArguments.MDUI_DESCRIPTION_PREFIX+lang);
-        if (description != null) {
-            output.format("            <%s:%s %s:%s=\"%s\">%s</%s:%s>\n",
-                    SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME,
-                    XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
-                    lang, description,
-                    SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME);
-        }
-    }
-
-    /** Output Scope and MDUI.
-     * @param includeMDUI do we output the MDUI
+     * Load a certificate from a file in a standard format and produce a base64-encoded DER string.
+     * 
+     * @param file certificate file
+     * 
+     * @return encoded string
+     * 
+     * @throws CertificateException if unable to decode
+     * @throws EncodingException if unable to encode
      */
-    private void outputExtensions(final boolean includeMDUI) {
-        final Environment env = getApplicationContext().getEnvironment();
-        final String scope = StringSupport.trimOrNull(env.getProperty("idp.scope"));
-        if (!includeMDUI && scope == null) {
-                return;
-        }
-        output.format("        <%s>\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
-        if (scope != null) {
-            output.format("          <%s:%s regexp=\"false\">%s</%s:%s>\n",
-                    ExtensionsConstants.SHIB_MDEXT10_PREFIX, Scope.DEFAULT_ELEMENT_LOCAL_NAME,
-                    scope,
-                    ExtensionsConstants.SHIB_MDEXT10_PREFIX, Scope.DEFAULT_ELEMENT_LOCAL_NAME);
-        }
-
-        if (includeMDUI) {
-            output.format("          <%s:%s>\n",
-                    SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
-            final String mduiLangs = env.getProperty(MetadataGenCommandLineArguments.MDUI_LANGS_PROPERTY);
-            if (mduiLangs == null) {
-                output.format("\n<!--\n          Fill in the details for your IdP here\n\n");
-                output.format("            <%s:%s %s:%s=\"en\">A Name for the IdP</%s:%s>\n",
-                        SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME,
-                        XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
-                        SAMLConstants.SAML20MDUI_PREFIX, DisplayName.DEFAULT_ELEMENT_LOCAL_NAME);
-                output.format("            <%s:%s %s:%s=\"en\">A Description for the IdP</%s:%s>\n",
-                        SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME,
-                        XMLConstants.XML_PREFIX, LangBearing.XML_LANG_ATTR_LOCAL_NAME,
-                        SAMLConstants.SAML20MDUI_PREFIX, Description.DEFAULT_ELEMENT_LOCAL_NAME);
-            } else {
-                for (final String lang:mduiLangs.split(" ")) {
-                    outputMDUI(lang);
-                }
-            }
-            final String logoY = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_HEIGHT, "80");
-            final String logoX = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_WIDTH, "80");
-            final String logoPath = env.getProperty(MetadataGenCommandLineArguments.MDUI_LOGO_PATH, "/path/to/logo");
-            output.format("            <%s:%s height=\"%s\" width=\"%s\">https://%s%s</%s:%s>\n",
-                    SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME,
-                    logoY, logoX,  getDnsName(), logoPath,
-                    SAMLConstants.SAML20MDUI_PREFIX, Logo.DEFAULT_ELEMENT_LOCAL_NAME);
-            if (mduiLangs == null) {
-                output.format("-->\n\n");
-            }
-            output.format("          </%s:%s>\n", SAMLConstants.SAML20MDUI_PREFIX, UIInfo.DEFAULT_ELEMENT_LOCAL_NAME);
-        }
-        output.format("\n        </%s>\n\n", Extensions.DEFAULT_ELEMENT_LOCAL_NAME);
+    @Nonnull private String getEncodedCertificate(@Nonnull final File file)
+            throws CertificateException, EncodingException {
+        final X509Certificate cert = X509Support.decodeCertificate(file);
+        return Base64Support.encode(cert.getEncoded(), true);
     }
-
-    /** Output Logout end points. */
-    private void outputLogoutEndpoints() {
-        output.format("        <%s Binding=\"%s\""
-                + " Location=\"https://%s/idp/profile/SAML2/Redirect/SLO\"/>\n",
-                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_REDIRECT_BINDING_URI,
-                getDnsName());
-        output.format("        <%s Binding=\"%s\""
-                + " Location=\"https://%s/idp/profile/SAML2/POST/SLO\"/>\n",
-                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_POST_BINDING_URI,
-                getDnsName());
-        output.format("        <%s Binding=\"%s\""
-                + " Location=\"https://%s/idp/profile/SAML2/POST/SLO-SimpleSign\"/>\n",
-                SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
-                getDnsName());
-
-        final List<String> backCert = parameters.getBackchannelCert();
-        if (backCert != null && !backCert.isEmpty()) {
-            output.format("        <%s Binding=\"%s\""
-                    + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/SLO\"/>\n",
-                    SingleLogoutService.DEFAULT_ELEMENT_LOCAL_NAME,
-                    SAMLConstants.SAML2_SOAP11_BINDING_URI,
-                    getDnsName());
-        }
-    }
-
-    /** Output Artifact Endpoints. */
-    private void outputArtifactEndpoints() {
-        if (args.isSaml1()) {
-            output.format("        <%s Binding=\"%s\""
-                    + " Location=\"https://%s:8443/idp/profile/SAML1/SOAP/ArtifactResolution\" index=\"1\"/>\n",
-                    ArtifactResolutionService.DEFAULT_ELEMENT_LOCAL_NAME,
-                    SAMLConstants.SAML1_SOAP11_BINDING_URI,
-                    getDnsName());
-        }
-        output.format("        <%s Binding=\"%s\""
-                + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/ArtifactResolution\" index=\"2\"/>\n",
-                ArtifactResolutionService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_SOAP11_BINDING_URI,
-                getDnsName());
-    }
-
-    /** Output SSO Endpoints. */
-    private void outputSSOEndpoints() {
-        if (args.isSaml1()) {
-            output.format("        <%s Binding=\"%s\""
-                    + " Location=\"https://%s/idp/profile/Shibboleth/SSO\"/>\n",
-                    SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
-                    "urn:mace:shibboleth:1.0:profiles:AuthnRequest",
-                    getDnsName());
-
-        }
-        output.format("        <%s Binding=\"%s\""
-                + " %s:%s=\"true\""
-                + " Location=\"https://%s/idp/profile/SAML2/POST/SSO\"/>\n",
-                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_POST_BINDING_URI,
-                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
-                getDnsName());
-        output.format("        <%s Binding=\"%s\""
-                + " %s:%s=\"true\""
-                + " Location=\"https://%s/idp/profile/SAML2/POST-SimpleSign/SSO\"/>\n",
-                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI,
-                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
-                getDnsName());
-        output.format("        <%s Binding=\"%s\""
-                + " %s:%s=\"true\""
-                + " Location=\"https://%s/idp/profile/SAML2/Redirect/SSO\"/>\n",
-                SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_REDIRECT_BINDING_URI,
-                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME,
-                getDnsName());
+    
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull @Unmodifiable @NotLive protected List<Resource> getAdditionalSpringResources() {
+        final Resource internal = new ClassPathResource("/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml");
+        
+        return Stream.concat(Stream.of(internal),
+                super.getAdditionalSpringResources().stream()).collect(
+                        CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
     }
 
     /**
-     * Write the <IDPSSODescriptor>.
+     * Builds the Velocity template context.
+     * 
+     * @return the populated context
      */
-    private void outputIDPSSO() {
-        final List<String> protocols = new ArrayList<>(4);
-        if (!args.isSaml1() && !args.isSaml2()) {
-            return;
-        }
-        if (args.isSaml1()) {
-            protocols.add(SAMLConstants.SAML20P_NS);
+    @Nonnull private VelocityContext getVelocityContext() {
+        final VelocityContext context = new VelocityContext();
+        context.put("entityID", args.getEntityID());
+        context.put("omitNamespaces", args.isOmitNamespaces());
+        if (!args.isOmitNamespaces()) {
+            final Map<String,String> prefixMap = new HashMap<>();
+            prefixMap.put(SAMLConstants.SAML20MD_PREFIX, SAMLConstants.SAML20MD_NS);
+            prefixMap.put(SAMLConstants.SAML20_PREFIX, SAMLConstants.SAML20_NS);
+            prefixMap.put(SignatureConstants.XMLSIG_PREFIX, SignatureConstants.XMLSIG_NS);
+            context.put("namespaces", prefixMap);
         }
-        if (args.isSaml2()) {
-            protocols.add(SAMLConstants.SAML20P_NS);
-            protocols.add(SAMLConstants.SAML11P_NS);
-            protocols.add("urn:mace:shibboleth:1.0");
-        }
-        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
-                IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
-                String.join(" ", protocols));
-
-        outputExtensions(true);
-
-        outputKeyDescriptors(true);
-
-        if (args.isArtifact()) {
-            outputArtifactEndpoints();
-        }
-
-        if (args.isLogout()) {
-            outputLogoutEndpoints();
-        }
-
-        outputSSOEndpoints();
-
-        output.format("    </%s>\n", IDPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
+        return context;
     }
 
     /**
-     * Write the <AttributeAuthorityDescriptor>.*/
-    private void outputAtttributeAuthorityDescriptor() {
-        final List<String> protocols;
-
-        if (args.isSaml1()) {
-            if (args.isAttributeFetch()) {
-                // Both
-                protocols = Arrays.asList(SAMLConstants.SAML20P_NS, SAMLConstants.SAML11P_NS);
-            } else {
-                // SAML1 only
-                protocols = Collections.singletonList(SAMLConstants.SAML11P_NS);
-            }
-        } else if (args.isAttributeFetch()) {
-            // SAML2 only
-            protocols = Collections.singletonList(SAMLConstants.SAML20P_NS);
-        } else {
-            // Neither
-            return;
-        }
-
-        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
-                AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
-                String.join(" ", protocols));
-
-        outputExtensions(false);
-        outputKeyDescriptors(true);
-        if (args.isSaml1()) {
-            output.format("        <%s Binding=\"%s\""
-                    + " Location=\"https://%s:8443/idp/profile/SAML1/SOAP/AttributeQuery\"/>\n",
-                    AttributeService.DEFAULT_ELEMENT_LOCAL_NAME,
-                    SAMLConstants.SAML1_SOAP11_BINDING_URI,
-                    getDnsName());
-        }
-        if (args.isAttributeFetch()) {
-            output.format("        <%s Binding=\"%s\""
-                    + " Location=\"https://%s:8443/idp/profile/SAML2/SOAP/AttributeQuery\"/>\n",
-                    AttributeService.DEFAULT_ELEMENT_LOCAL_NAME,
-                    SAMLConstants.SAML2_SOAP11_BINDING_URI,
-                    getDnsName());
-        }
-        output.format("    </%s>\n", AttributeAuthorityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
-    }
-
-    /**
-     * Write the <SPSSODescriptor>.
-     */
-    private void outputSPSSO() {
-        if (!args.isSamlSP()) {
-            return;
-        }
-        output.format("    <%s protocolSupportEnumeration=\"%s\">\n",
-                SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML20P_NS);
-
-        outputKeyDescriptors(false);
-
-        output.format("        <%s Binding=\"%s\""
-                + " Location=\"https://%s/idp/profile/Authn/SAML2/POST/SSO\" index=\"0\"/>\n",
-                AssertionConsumerService.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML2_POST_BINDING_URI,
-                getDnsName());
-
-        output.format("    </%s>\n", SPSSODescriptor.DEFAULT_ELEMENT_LOCAL_NAME);
-    }
-
-
-    /** Output the metadata.
-     * @return true iff this worked.
-     */
-    private int outputMetadata() {
-        output.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-        output.format("   <!--\n");
-        output.format("     This is example metadata only. Do *NOT* supply it as is without review,\n");
-        output.format("     and do *NOT* provide it in real time to your partners.\n");
-        output.format("     This metadata is not dynamic - run metadatagen again to recreate.\n");
-        output.format("     Created: %s\n   -->\n", Instant.now().toString());
-        output.format("<%s  xmlns=\"%s\" xmlns:%s=\"%s\"\n", EntityDescriptor.DEFAULT_ELEMENT_LOCAL_NAME,
-                SAMLConstants.SAML20MD_NS,
-                SignatureConstants.XMLSIG_PREFIX, SignatureConstants.XMLSIG_NS);
-        output.format("      xmlns:%s=\"%s\" xmlns:%s=\"%s\"\n",
-                ExtensionsConstants.SHIB_MDEXT10_PREFIX, ExtensionsConstants.SHIB_MDEXT10_NS,
-                XMLConstants.XML_PREFIX, XMLConstants.XML_NS);
-        output.format("      xmlns:%s=\"%s\" xmlns:%s=\"%s\"\n",
-                SAMLConstants.SAML20MDUI_PREFIX, SAMLConstants.SAML20MDUI_NS,
-                SAMLConstants.SAML20PREQ_ATTRR_PREFIX, SAMLConstants.SAML20PREQ_ATTR_NS);
-        output.format(" validUntil=\"%s\" entityID=\"%s\">\n\n",
-                DOMTypeSupport.instantToString(Instant.now()), 
-                getApplicationContext().getEnvironment().getProperty("idp.entityID", "idp.example.org"));
-        outputIDPSSO();
-        outputAtttributeAuthorityDescriptor();
-        outputSPSSO();
-        output.format("</EntityDescriptor>\n");
-        output.flush();
-        output.close();
-        return RC_OK;
-    }
-    
-    /** Lookup the dns name with a default and cache it.
-     *  @return the dns name
-     */
-    @Nonnull String getDnsName() {
-        if (dnsName == null) {
-            dnsName = getApplicationContext().
-                    getEnvironment().
-                    getProperty(MetadataGenCommandLineArguments.DNS_NAME_PROPERTY, "idp.example.org");
-        }
-        assert dnsName != null;
-        return dnsName;
-    }
-
-    /** Build the {@link MetadataGenCLI#parameters} object.
+     * Set up {@link MetadataGenCLI#output}.
      * 
-     * This does a lot of work which migbt be expected to be in {@link MetadataGenCommandLineArguments#validate()},
-     * However at that point we do not have access to properties which change things some what.
-     * @return true iff this worked and if everything needed was there.
-     */
-    // Checkstyle: CyclomaticComplexity OFF
-    private boolean populateParameters() {
-        try {
-            parameters = getApplicationContext().getBean(MetadataGenParameters.class);
-        } catch (final NoSuchBeanDefinitionException e) {
-            getLogger().error("Could not locate IdPConfiguration");
-            return false;
-        }
-        
-        final List<String> backCert = parameters.getBackchannelCert();
-        final boolean hasBackChannel = backCert != null && !backCert.isEmpty();
-        boolean worked = true;
-        if (args.isArtifact() && !hasBackChannel) {
-            getLogger().error("Must specify --backChannel <path> if +artifact speificied");
-            worked = false;
-        }
-        if (args.isAttributeFetch() && !hasBackChannel) {
-            getLogger().error("Must specify --backChannel <path> if +attributeFetch speificied");
-            worked = false;
-        }
-        if (hasBackChannel && !args.isAttributeFetch() && !args.isArtifact()  && !args.isSaml1()) {
-            getLogger().error("--backChannel <path> requires +artifact and/or +attributeFetch and/or +saml1");
-            worked = false;
-        }
-        return worked;
-    }
-    // Checkstyle: CyclomaticComplexity ON
-
-
-    /** Set up {@link MetadataGenCLI#output}.
-     * @return true iff this worked.
+     * @return true iff this worked
      */
     private boolean setupWriter() {
-        if (args.getOutput() == null) {
+        if (args.getOutputFile() == null) {
             output = new PrintWriter(System.out);
         } else {
-            final File out = new File(args.getOutput());
+            final File out = new File(args.getOutputFile());
             try {
                 final FileOutputStream outStream = new FileOutputStream(out);
                 output = new PrintWriter(new BufferedOutputStream(outStream));
             } catch (final IOException e) {
-                getLogger().error("Could not open {}", args.getOutput(), e);
+                getLogger().error("Could not open {}", args.getOutputFile(), e);
                 return false;
             }
         }
@@ -530,15 +545,28 @@ public final class MetadataGenCLI extends AbstractIdPHomeAwareCommandLine<Metada
         if (ret != RC_OK) {
             return ret;
         }
+        
         if (!setupWriter()) {
             return RC_IO;
         }
-        if (!populateParameters()) {
+        
+        try {
+            velocityEngine = getApplicationContext().getBean(VELOCITY_ENGINE_BEAN_NAME, VelocityEngine.class);
+        } catch (final BeansException e) {
+            getLogger().error("Unable to acquire Velocity engine bean", e);
+            return RC_INIT;
+        }
+        
+        try {
+            Template.fromTemplateName(velocityEngine,
+                    "/metadatagen-templates/EntityDescriptor.vm").merge(getVelocityContext(), output);
+        } catch (final Exception e) {
+            getLogger().error("Error generating output", e);
             return RC_IO;
         }
-        final int i = outputMetadata();
-        this.output.close();
-        return i;
+        
+        output.close();
+        return RC_OK;
     }
 
     /** 
diff --git a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java
index 99dd42b..c5a4d8c 100644
--- a/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java
+++ b/metadatagen-impl/src/main/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenCommandLineArguments.java
@@ -16,27 +16,26 @@
  */
 package net.shibboleth.idp.plugin.metadatagen.impl;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Properties;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.opensaml.saml.saml2.metadata.ArtifactResolutionService;
+import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
+import org.opensaml.saml.saml2.metadata.AttributeService;
+import org.opensaml.saml.saml2.metadata.SingleLogoutService;
+import org.opensaml.saml.saml2.metadata.SingleSignOnService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.beust.jcommander.Parameter;
 
 import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLineArguments;
+import net.shibboleth.shared.annotation.constraint.Live;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
 
 /**
  * Command line arguments for Metadata Generation.
@@ -70,133 +69,344 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
     /** Logger. */
     @Nullable private Logger log;
 
-    /** Do we output SAML2. */
-    @Parameter(names = { "+saml2", "+2", "+SAML2"} )
+    /** The unique ID. */
+    @Parameter(names = {"--entityID", "--id"}, required=true, description="Unique ID for entity")
+    @Nullable private String entityID;
+    
+    /** Do we output SAML 2.0? */
+    @Parameter(names = {"--saml2"}, description="Include SAML 2.0 support?")
     private boolean saml2;
 
-    /** Do we NOT output SAML2. */
-    @Parameter(names = { "-saml2", "-2", "-SAML2"} )
-    private boolean noSaml2;
+    /** Do we output CAS? */
+    @Parameter(names = {"--cas"}, description="Include CAS support?")
+    private boolean cas;
+
+    /** Do we output OIDC? */
+    @Parameter(names = {"--oidc"}, description="Include OpenID/OAuth support?")
+    private boolean oidc;
+
+    /** Do we output an SP role? */
+    @Parameter(names = {"--sp"}, description="Include Service Provider role?")
+    private boolean sp;
+
+    /** Do we output an SP role? */
+    @Parameter(names = {"--idp"}, description="Include Identity Provider role?")
+    private boolean idp;
+
+    /** Do we output an AA role?.*/
+    @Parameter(names = {"--aa"}, description="Include Attribute Authority role?")
+    private boolean aa;
+    
+    @Parameter(names = {"--sso-redirect"}, description="SingleSignOnService endpoint using HTTP-Redirect binding")
+    @Nullable private String ssoRedirect;
+
+    @Parameter(names = {"--sso-post"}, description="SingleSignOnService endpoint using HTTP-POST binding")
+    @Nullable private String ssoPost;
+
+    @Parameter(names = {"--sso-artifact"}, description="SingleSignOnService endpoint using HTTP-Artifact binding")
+    @Nullable private String ssoArtifact;
+
+    @Parameter(names = {"--sso-soap"}, description="SingleSignOnService endpoint using SOAP binding")
+    @Nullable private String ssoSoap;
 
-    /** Do we output SAM1.?*/
-    @Parameter(names = { "+saml1", "+1", "+SAML1"})
-    private boolean saml1;
+    @Parameter(names = {"--artifact-soap", "--artifact"}, description="ArtifactResolutionService endpoint using SOAP binding")
+    @Nullable private String artifactSoap;
 
-    /** Do we output for an SP.*/
-    @Parameter(names = { "+samlSP", "+sp", "+SP", "+SAMLSP"})
-    private boolean samlSP;
+    @Parameter(names = {"--attr-soap"}, description="AttributeService endpoint using SOAP binding")
+    @Nullable private String attributeSoap;
 
-    /** Do we output logout.*/
-    @Parameter(names = { "+logout", "+lo"})
-    private boolean logout;
+    @Parameter(names = {"--logout-redirect", "-LR"}, description="SingleLogoutService endpoint(s) using HTTP-Redirect binding")
+    @Nonnull private List<String> logoutRedirect = new ArrayList<>();
 
-    /** Do we output Artifact.*/
-    @Parameter(names = { "+artifact", "+artefact"})
-    private boolean artifact;
+    @Parameter(names = {"--logout-post", "-LP"}, description="SingleLogoutService endpoint(s) using HTTP-POST binding")
+    @Nonnull private List<String> logoutPost = new ArrayList<>();
 
-    /** Do we output for an Attribute Fetch.*/
-    @Parameter(names = { "+attributeFetch"})
-    private boolean attributeFetch;
+    @Parameter(names = {"--logout-artifact", "-LA"}, description="SingleLogoutService endpoint(s) using HTTP-Artifact binding")
+    @Nonnull private List<String> logoutArtifact = new ArrayList<>();
 
-    /** Certificate for (IdP) BackChannel (attribute, artifact, logout).*/
-    @Parameter(names = { "--backChannel", "-bc"})
-    @Nullable private String backChannelPath;
+    @Parameter(names = {"--logout-soap", "-LS"}, description="SingleLogoutService endpoint(s) using SOAP binding")
+    @Nonnull private List<String> logoutSoap = new ArrayList<>();
 
-    /** DNS name (for back channel addresses). */
-    @Parameter(names = { "--DNSName", "-d"})
-    @Nullable private String dnsName;
+    @Parameter(names = {"--acs", "--acs-post",  "-h"}, description="AssertionConsumerService endpoint(s) using HTTP-POST binding")
+    @Nonnull private List<String> acsPost = new ArrayList<>();
 
-    /** Output.*/
-    @Parameter(names = { "--output", "-o"})
-    @Nullable private String output;
+    @Parameter(names = {"--acs-artifact"}, description="AssertionConsumerService endpoint(s) using HTTP-Artifact binding")
+    @Nonnull private List<String> acsArtifact = new ArrayList<>();
 
-    /** Do we output SAML2 metadata?
-     * @return what.
+    @Parameter(names = {"--acs-paos", "--ecp"}, description="AssertionConsumerService endpoint(s) using PAOS binding")
+    @Nonnull private List<String> acsPaos = new ArrayList<>();
+    
+    /** Path(s) to dual-use certificate(s). */
+    @Parameter(names = {"--cert", "-c"}, description="Path(s) to certificate(s) for signing and encryption")
+    @Nonnull private List<String> certificatePaths = new ArrayList<>();
+
+    /** Path(s) to signing certificate(s). */
+    @Parameter(names = {"--signing", "-x"}, description="Path(s) to certificate(s) for signing only")
+    @Nonnull private List<String> signingPaths = new ArrayList<>();
+
+    /** Path(s) to encryption certificate(s). */
+    @Parameter(names = {"--encryption", "-e"}, description="Path(s) to certificate(s) for encryption only")
+    @Nonnull private List<String> encryptionPaths = new ArrayList<>();
+
+    /** Omit namespaces from root element? */
+    @Parameter(names = {"--omit-namespaces"}, description="Omit namespace declarations (assumes surrounding document context)")
+    private boolean omitNamespaces;
+    
+    /** Output file path. */
+    @Parameter(names = {"--file", "-f"}, description="Path to output file (stdout otherwise)")
+    @Nullable private String outputFile;
+
+    /**
+     * Get the unique ID.
+     * 
+     * @return argument value
      */
-    public boolean isSaml2() {
+    @Nullable public String getEntityID() {
+        return entityID;
+    }
+    
+    /**
+     * Do we output SAML 2.0 metadata?
+     * 
+     * @return argument value
+     */
+    public boolean isSAML2() {
         return saml2;
     }
 
-    /** Do we output SAML1 metadata?
-     * @return what.
+    /**
+     * Do we output CAS metadata?
+     * 
+     * @return argument value
      */
-    public boolean isSaml1() {
-        return saml1;
+    public boolean isCAS() {
+        return cas;
     }
 
-    /** Do we output SAML SP metadata.
-     * @return what.
+    /**
+     * Do we output OIDC/OAuth metadata?
+     * 
+     * @return argument value
      */
-    public boolean isSamlSP() {
-        return samlSP;
+    public boolean isOIDC() {
+        return oidc;
     }
 
-    /** Do we output Logout metadata?
-     * @return what.
+    /**
+     * Do we output SP role?
+     * 
+     * @return argument value
      */
-    public boolean isLogout() {
-        return logout;
+    public boolean isSP() {
+        return sp;
     }
 
-    /** Do we output Artifact metadata?
-     * @return what.
+    /**
+     * Do we output IdP role?
+     * 
+     * @return argument value
      */
-    public boolean isArtifact() {
-        return artifact;
+    public boolean isIDP() {
+        return idp;
     }
 
-    /** Do we output Attribute Fetch metadata?
-     * @return what.
+    /**
+     * Do we output AA role?
+     * 
+     * @return argument value
      */
-    public boolean isAttributeFetch() {
-        return attributeFetch;
+    public boolean isAA() {
+        return aa;
+    }
+    
+    /**
+     * Get endpoint expression for {@link SingleSignOnService} endpoints using SAML 2.0 HTTP-Redirect binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nullable String getSSORedirectEndpoint() {
+        return ssoRedirect;
     }
 
-    /** Where to put the data.
-     * @return where
+    /**
+     * Get endpoint expression for {@link SingleSignOnService} endpoint using SAML 2.0 HTTP-POST binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
      */
-    @Nullable public String getOutput() {
-        return output;
+    @Nullable String getSSOPostEndpoint() {
+        return ssoPost;
+    }
+    
+    /**
+     * Get endpoint expression for {@link SingleSignOnService} endpoint using SAML 2.0 HTTP-Artifact binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nullable String getSSOArtifactEndpoint() {
+        return ssoPost;
     }
 
     /**
-     * {@inheritDoc}
+     * Get endpoint expression for {@link SingleSignOnService} endpoint using SAML 2.0 SOAP binding.
      * 
-     * We override this to add a property file of our own making for
-     * the backchannel (if needed) and dnsname.
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
      */
-    @Nonnull @Unmodifiable @NotLive public List<String> getPropertyFiles() {
-        final List<String> fromCmdline = super.getPropertyFiles();
-        if (dnsName == null && backChannelPath == null) {
-            return fromCmdline;
-        }
+    @Nullable String getSSOSoapEndpoint() {
+        return ssoPost;
+    }
 
-        final Properties props = new Properties(2);
-        if (dnsName != null) {
-            props.setProperty(DNS_NAME_PROPERTY, dnsName);
-        }
-        if (backChannelPath != null) {
-            props.setProperty(BACKCHANNEL_PROPERTY, backChannelPath);
-        }
+    /**
+     * Get endpoint expression for {@link ArtifactResolutionService} endpoint using SAML 2.0 SOAP binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nullable String getArtifactSoapEndpoint() {
+        return artifactSoap;
+    }
 
-        final List<String> result = new ArrayList<>(fromCmdline.size() + 1);
-
-        try {
-            final File file = File.createTempFile("MetadataGen", ".properties");
-            file.deleteOnExit();
-            try (final FileOutputStream out = new FileOutputStream(file)) {
-                props.store(out, "created");
-            }
-            if (fromCmdline.isEmpty()) {
-                return CollectionSupport.singletonList(file.getAbsolutePath());
-            }
-            result.addAll(fromCmdline);
-            result.add(file.getAbsolutePath());
-        } catch (final IOException e) {
-            getLog().error("Could not generate property file", e);
-        }
+    /**
+     * Get endpoint expression for {@link AttributeService} endpoint using SAML 2.0 SOAP binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nullable String getAttributeSoapEndpoint() {
+        return attributeSoap;
+    }
+
+    /**
+     * Get endpoint expression(s) for {@link SingleLogoutService} endpoint(s) using SAML 2.0 HTTP-Redirect binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getLogoutRedirectEndpoints() {
+        return logoutRedirect;
+    }
 
-        return result;
+    /**
+     * Get endpoint expression(s) for {@link SingleLogoutService} endpoint(s) using SAML 2.0 HTTP-POST binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getLogoutPostEndpoints() {
+        return logoutPost;
+    }
+
+    /**
+     * Get endpoint expression(s) for {@link SingleLogoutService} endpoint(s) using SAML 2.0 HTTP-Artifact binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getLogoutArtifactEndpoints() {
+        return logoutArtifact;
+    }
+
+    /**
+     * Get endpoint expression(s) for {@link SingleLogoutService} endpoint(s) using SAML 2.0 SOAP binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getLogoutSoapEndpoints() {
+        return logoutSoap;
+    }
+
+    /**
+     * Get endpoint expression(s) for {@link AssertionConsumerService} endpoint(s) using SAML 2.0 HTTP-POST binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getACSPostEndpoints() {
+        return acsPost;
+    }
+
+    /**
+     * Get endpoint expression(s) for {@link AssertionConsumerService} endpoint(s) using SAML 2.0 HTTP-Artifact binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getACSArtifactEndpoints() {
+        return acsPost;
+    }
+
+    /**
+     * Get endpoint expression(s) for {@link AssertionConsumerService} endpoint(s) using SAML 2.0 PAOS binding.
+     * 
+     * <p>Endpoints may omit scheme but must specify port and full path.</p>
+     * 
+     * @return argument value
+     */
+    @Nonnull @Live List<String> getACSPaosEndpoints() {
+        return acsPaos;
+    }
+
+    /**
+     * Dual-use certificate path(s).
+     * 
+     * @return argument value
+     */
+    @Nullable @Live public List<String> getCertificatePaths() {
+        return certificatePaths;
+    }
+
+    /**
+     * Signing-only certificate path(s).
+     * 
+     * @return argument value
+     */
+    @Nullable @Live public List<String> getSigningCertificatePaths() {
+        return signingPaths;
+    }
+
+    /**
+     * Encryption-only certificate path(s).
+     * 
+     * @return argument value
+     */
+    @Nullable @Live public List<String> getEncryptionCertificatePaths() {
+        return encryptionPaths;
+    }
+
+    /**
+     * Gets whether to omit namespaces on root element.
+     * 
+     * <p>Used to optimize namespace declarations when generating batches.</p>
+     * 
+     * @return argument value
+     */
+    public boolean isOmitNamespaces() {
+        return omitNamespaces;
+    }
+    
+    /**
+     * Output file path (stdout used otherwise).
+     * 
+     * @return argument value
+     */
+    @Nullable public String getOutputFile() {
+        return outputFile;
     }
 
     /** {@inheritDoc} */
@@ -212,35 +422,52 @@ public class MetadataGenCommandLineArguments extends AbstractIdPHomeAwareCommand
     /** {@inheritDoc} */
     @Override
     public void validate() throws IllegalArgumentException {
-        if (!saml2 && noSaml2) {
-            saml2 = false;
-        } else {
+        if (!saml2 && !cas && !oidc) {
             saml2 = true;
         }
+        
+        if (!sp && !idp && !aa) {
+            sp = true;
+        }
     }
 
     /** {@inheritDoc} */
     @Override
     public void printHelp(@Nonnull final PrintStream out) {
         super.printHelp(out);
-        out.println(String.format("  %-20s %s", "+SAML1, +1",
-                                  "Output SAML1 Metadata."));
-        out.println(String.format("  %-20s %s", "-SAML2, -2",
-                                  "do NOT Output SAML2 Metadata."));
-        out.println(String.format("  %-20s %s", "+SP, +SAMLSP",
-                                  "Output SAML2 SP Metadata."));
-        out.println(String.format("  %-20s %s", "+logout",
-                                  "Output Logout Metadata."));
-        out.println(String.format("  %-20s %s", "+artifact",
-                                  "Output SAML artifact Metadata (requires -bc),"));
-        out.println(String.format("  %-20s %s", "+attributeFetch",
-                                  "Output SAML attributeFetch Metadata  (requires -bc)."));
-        out.println(String.format("  %-20s %s", "-bc, --backchannel <Path>",
-                                  "Path to backchannel certificate"));
-        out.println(String.format("  %-20s %s", "-d, --DNSName name",
-                                  "DNS name to use in back channel addresses (default idp.example.org)"));
-        out.println(String.format("  %-20s %s", "--output, -o",
-                                  "Output location."));
+        out.println(String.format("  %-20s %s", "--entityID, --id", "The entityID (or client_id, etc.)"));
+        
+        out.println(String.format("  %-20s %s", "--saml2", "Output SAML 2.0 metadata."));
+        out.println(String.format("  %-20s %s", "--cas", "Output CAS metadata."));
+        out.println(String.format("  %-20s %s", "--oidc", "Output OIDC metadata."));
+
+        out.println(String.format("  %-20s %s", "--sp", "Output SP role."));
+        out.println(String.format("  %-20s %s", "--idp", "Output IdP role."));
+        out.println(String.format("  %-20s %s", "--aa", "Output Attribute Authority role."));
+
+        out.println(String.format("  %-20s %s", "--sso-redirect", "Endpoint for SAML 2.0 SSO HTTP-Redirect endpoint"));
+        out.println(String.format("  %-20s %s", "--sso-post", "Endpoint for SAML 2.0 SSO HTTP-POST endpoint"));
+        out.println(String.format("  %-20s %s", "--sso-artifact", "Endpoint for SAML 2.0 SSO HTTP-Artifact endpoint"));
+        out.println(String.format("  %-20s %s", "--sso-soap", "Endpoint for SAML 2.0 SSO SOAP endpoint"));
+
+        out.println(String.format("  %-20s %s", "--artifact-soap, --artifact", "Endpoint for SAML 2.0 Artifact Resolution SOAP endpoint"));
+        out.println(String.format("  %-20s %s", "--attr-soap", "Endpoint for SAML 2.0 Attribute Query SOAP endpoint"));
+
+        out.println(String.format("  %-20s %s", "--logout-redirect", "Endpoint for SAML 2.0 SLO HTTP-Redirect endpoint"));
+        out.println(String.format("  %-20s %s", "--logout-post", "Endpoint for SAML 2.0 SLO HTTP-POST endpoint"));
+        out.println(String.format("  %-20s %s", "--logout-artifact", "Endpoint for SAML 2.0 SLO HTTP-Artifact endpoint"));
+        out.println(String.format("  %-20s %s", "--logout-soap", "Endpoint for SAML 2.0 SLO SOAP endpoint"));
+
+        out.println(String.format("  %-20s %s", "--acs, --acs-post, -h", "Endpoint for SAML 2.0 ACS HTTP-POST endpoint"));
+        out.println(String.format("  %-20s %s", "--acs-artifact", "Endpoint for SAML 2.0 ACS HTTP-Artifact endpoint"));
+        out.println(String.format("  %-20s %s", "--ecp, --acs-paos", "Endpoint for SAML 2.0 ACS ECP/PAOS endpoint"));
+
+        out.println(String.format("  %-20s %s", "--cert, -c", "Path to dual-use certificate."));
+        out.println(String.format("  %-20s %s", "--signing, -x", "Path to signing certificate."));
+        out.println(String.format("  %-20s %s", "--encryption, -e", "Path to encryption certificate."));
+        
+        out.println(String.format("  %-20s %s", "--omit-namespaces", "Omit namespaces on root element."));
+        out.println(String.format("  %-20s %s", "--output, -o", "Output file path."));
         out.println();
     }
     
diff --git a/metadatagen-impl/src/main/resources/metadatagen-templates/EntityDescriptor.vm b/metadatagen-impl/src/main/resources/metadatagen-templates/EntityDescriptor.vm
new file mode 100644
index 0000000..9cf6857
--- /dev/null
+++ b/metadatagen-impl/src/main/resources/metadatagen-templates/EntityDescriptor.vm
@@ -0,0 +1,6 @@
+## Open up new EntityDescriptor
+<md:EntityDescriptor#if (!$omitNamespaces)#foreach ($ns in $namespaces.entrySet()) xmlns:$ns.key="$ns.value"#end#end entityID="${entityID}">
+
+## All the interior bits....
+
+</md:EntityDescriptor>
diff --git a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml
new file mode 100644
index 0000000..449d116
--- /dev/null
+++ b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/conf/velocity.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:c="http://www.springframework.org/schema/c"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+
+    <!-- Needed to allow for auto-wiring of descriptors and other components. -->
+    <context:annotation-config />
+
+    <bean id="shibboleth.PropertySourcesPlaceholderConfigurer"
+        class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+        p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+    <!-- This bean MUST be called "conversionService" to work properly. -->
+    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
+        <property name="converters">
+            <set>
+                <bean class="net.shibboleth.shared.spring.config.StringToIPRangeConverter" />
+                <bean class="net.shibboleth.shared.spring.config.BooleanToPredicateConverter" />
+                <bean class="net.shibboleth.shared.spring.config.StringBooleanToPredicateConverter" />
+                <bean class="net.shibboleth.shared.spring.config.StringToResourceConverter" />
+                <bean class="net.shibboleth.shared.spring.config.StringToDurationConverter" />
+                <bean class="net.shibboleth.shared.spring.config.StringToPeriodConverter" />
+            </set>
+        </property>
+    </bean>
+
+    <!-- This BeanPostProcessor auto-sets identifiable beans with the bean name (if not already set). -->
+    <bean id="shibboleth.IdentifiableBeanPostProcessor"
+        class="net.shibboleth.shared.spring.config.IdentifiableBeanPostProcessor" />
+
+    <util:map id="shibboleth.DefaultVelocityEngineProperties">
+        <entry key="parser.space_gobbling" value="%{idp.velocity.space.gobbling:bc}" />
+        <entry key="resource.loaders" value="file, classpath" />
+        <entry key="resource.loader.classpath.class" value="org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader" />
+        <entry key="resource.loader.file.class" value="org.apache.velocity.runtime.resource.loader.FileResourceLoader" />
+        <entry key="resource.loader.file.path" value="#{'%{idp.views:%{idp.home}/views}'.trim()}" />
+        <entry key="resource.loader.file.cache" value="%{idp.velocity.file.cache:false}" />
+        <entry key="runtime.strict_mode.enable" value="%{idp.velocity.runtime.strictmode:true}" />
+    </util:map>
+
+    <bean id="shibboleth.VelocityEngine" class="net.shibboleth.shared.spring.velocity.VelocityEngineFactoryBean"
+        p:velocityPropertiesMap="#{getObject('shibboleth.VelocityEngineProperties') ?: getObject('shibboleth.DefaultVelocityEngineProperties')}" />
+
+ </beans>
diff --git a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/plugin.properties b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/plugin.properties
index 4044597..a846f00 100644
--- a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/plugin.properties
+++ b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/impl/plugin.properties
@@ -2,7 +2,7 @@
 
 plugin.id = net.shibboleth.idp.plugin.metadatagen
 # Only used when package manifest is not available
-plugin.version = 1.0.1
+plugin.version = 2.0.0
 
 # No prereqs
 #plugin.modules.required =
diff --git a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/metadatagen.xml b/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/metadatagen.xml
deleted file mode 100644
index 98ad360..0000000
--- a/metadatagen-impl/src/main/resources/net/shibboleth/idp/plugin/metadatagen/metadatagen.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:context="http://www.springframework.org/schema/context"
-	xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
-	xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
-                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
-                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
-    default-init-method="initialize"
-    default-destroy-method="destroy">
-
-	<bean
-		class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
-		p:placeholderPrefix="%{" p:placeholderSuffix="}" />
-
-    <context:property-placeholder />
-
-	<bean id="IdPConfiguration"
-		class="net.shibboleth.idp.plugin.metadatagen.impl.MetadataGenParameters"
-		p:encryptionCertResource="%{idp.encryption.cert}"
-        p:signingCertResource="%{idp.signing.cert}"
-        p:backchannelCertResource="#{ environment.containsProperty('idp.metadata.backchannel.cert')  ? '%{idp.metadata.backchannel.cert:0}' : null}" />
-</beans>
\ No newline at end of file
diff --git a/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java b/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java
index 1f73ed9..c273d0e 100644
--- a/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java
+++ b/metadatagen-impl/src/test/java/net/shibboleth/idp/plugin/metadatagen/impl/MetadataGenTest.java
@@ -32,17 +32,16 @@ import org.testng.annotations.Test;
 
 import net.shibboleth.shared.cli.AbstractCommandLine;
 
-
 @SuppressWarnings("javadoc")
 public class MetadataGenTest {
-	
+    
     // To run the test
     //   1) Set the next two static fields
     //   2) Edit the setting of the property 'idp.metadata.backchannel.cert' in
     //      src/test/resources/net/shibboleth/idp/plugin/metadatagen/impl/extra2
-    private final static String IDP_HOME =  "H:/Downloads/idp";
+    private final static String IDP_HOME =  "/Users/scantor/Documents/shibboleth5/java-identity-provider/idp-conf-impl/src/main/resources/net/shibboleth/idp/module";
     private final boolean enabled = false;
-	
+    
     @Test(enabled = enabled) public void test() throws IOException {
         assertEquals(MetadataGenCLI.runMain(
                     new String[] {
@@ -57,20 +56,19 @@ public class MetadataGenTest {
     }
     
     @Test(enabled = enabled) public void testProps() throws IOException {
-    	final File file1 = Files.createTempFile(getClass().getName(), ".props").toFile();
-    	file1.deleteOnExit();
-    	final ClassPathResource res1 = new ClassPathResource("/net/shibboleth/idp/plugin/metadatagen/impl/extra1");
-    	try (final InputStream stream = res1.getInputStream(); final OutputStream out = new BufferedOutputStream(new FileOutputStream(file1))) {
-    		stream.transferTo(out);
-    	}
-    	final File file2 = Files.createTempFile(getClass().getName(), ".props").toFile();
-    	file2.deleteOnExit();
-    	final ClassPathResource res2 = new ClassPathResource("/net/shibboleth/idp/plugin/metadatagen/impl/extra2");
-    	try (final InputStream stream = res2.getInputStream(); final OutputStream out = new BufferedOutputStream(new FileOutputStream(file2))) {
-    		stream.transferTo(out);
-    	}
-    	
-    	
+        final File file1 = Files.createTempFile(getClass().getName(), ".props").toFile();
+        file1.deleteOnExit();
+        final ClassPathResource res1 = new ClassPathResource("/net/shibboleth/idp/plugin/metadatagen/impl/extra1");
+        try (final InputStream stream = res1.getInputStream(); final OutputStream out = new BufferedOutputStream(new FileOutputStream(file1))) {
+            stream.transferTo(out);
+        }
+        final File file2 = Files.createTempFile(getClass().getName(), ".props").toFile();
+        file2.deleteOnExit();
+        final ClassPathResource res2 = new ClassPathResource("/net/shibboleth/idp/plugin/metadatagen/impl/extra2");
+        try (final InputStream stream = res2.getInputStream(); final OutputStream out = new BufferedOutputStream(new FileOutputStream(file2))) {
+            stream.transferTo(out);
+        }
+        
         assertEquals(MetadataGenCLI.runMain(
                     new String[] {
                             "--home", IDP_HOME,
@@ -78,10 +76,9 @@ public class MetadataGenTest {
                             "+saml1",
                             "--propertyFiles", file1.getPath() +","+ file2.getPath(),
                             "+attributeFetch","+artifact", "+logout",
-                    		}),
+                            }),
                 AbstractCommandLine.RC_OK);
     }
-
     
     @Test(enabled = enabled) public void noBc() throws IOException {
         assertEquals(MetadataGenCLI.runMain(
@@ -92,7 +89,6 @@ public class MetadataGenTest {
                 AbstractCommandLine.RC_OK);
     }
 
-
     @Test(enabled = enabled) public void help() throws IOException {
         assertEquals(MetadataGenCLI.runMain(
                     new String[] {
@@ -101,4 +97,14 @@ public class MetadataGenTest {
                 AbstractCommandLine.RC_OK);
     }
 
+    @Test(enabled = true) public void testSimple() {
+        assertEquals(MetadataGenCLI.runMain(
+                new String[] {
+                        "--home", IDP_HOME,
+                        "--sp",
+                        "--entityID", "https://sp.example.org",
+                        }),
+            AbstractCommandLine.RC_OK);
+    }
+    
 }
\ No newline at end of file
diff --git a/metadatagen-impl/src/test/resources/logback-test.xml b/metadatagen-impl/src/test/resources/logback-test.xml
index a58ce3f..1e2200b 100644
--- a/metadatagen-impl/src/test/resources/logback-test.xml
+++ b/metadatagen-impl/src/test/resources/logback-test.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <configuration>
-    <logger name="net.shibboleth" level="ALL"/>
-    <logger name="org.springframework" level="DEBUG"/>
+    <logger name="net.shibboleth" level="DEBUG"/>
+    <logger name="org.springframework" level="INFO"/>
 
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
@@ -12,7 +12,7 @@
     </appender>
 
     <root>
-        <level value="ALL" />
+        <level value="INFO" />
         <appender-ref ref="STDOUT" />
     </root>
     
diff --git a/pom.xml b/pom.xml
index 0a0ea43..22e8957 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,11 +67,6 @@
     <dependencyManagement>
         <!-- metadatagen project dependencies -->
         <dependencies>
-            <dependency>
-                <groupId>net.shibboleth.idp.plugin.metadatagen</groupId>
-                <artifactId>idp-plugin-metadatagen-api</artifactId>
-                <version>${project.version}</version>
-            </dependency>
             <dependency>
                 <groupId>net.shibboleth.idp.plugin.metadatagen</groupId>
                 <artifactId>idp-plugin-metadatagen-impl</artifactId>
@@ -118,7 +113,6 @@
     
     <modules>
         <module>metadatagen-impl</module>
-        <module>metadatagen-api</module>
         <!-- All DIST modules must come after the -api and -impl modules -->
         <module>metadatagen-dist</module>
     </modules>
diff --git a/checkstyle.xml b/resources/checkstyle/checkstyle.xml
similarity index 100%
rename from checkstyle.xml
rename to resources/checkstyle/checkstyle.xml

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


More information about the commits mailing list