[java-identity-provider] branch main updated: IDP-1664 - Support Module service API

Scott Cantor cantor.2 at osu.edu
Tue Sep 8 19:19:41 UTC 2020


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

scantor pushed a commit to branch main
in repository java-identity-provider.

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

The following commit(s) were added to refs/heads/main by this push:
       new  ad65818cb IDP-1664 - Support Module service API
ad65818cb is described below

commit ad65818cbc67e2c0526cd3d926310b96b45de784
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Sep 8 15:19:32 2020 -0400

    IDP-1664 - Support Module service API
    
    https://issues.shibboleth.net/jira/browse/IDP-1664
    
    Add resource reporting to enable/disable API.
    Add reporting and module messaging to CLI.
    Add ANSI color for non-Windows output.
---
 .../shibboleth/idp/module/AbstractIdPModule.java   |  86 +++++++++++++----
 .../java/net/shibboleth/idp/module/IdPModule.java  |  32 ++++++-
 .../idp/module/PropertyDrivenIdPModule.java        |  16 +++-
 .../.settings/org.eclipse.jdt.core.prefs           |   2 +-
 .../idp/module/impl/ModuleManagerArguments.java    |   3 +-
 .../idp/module/impl/ModuleManagerCLI.java          | 103 +++++++++++++++++++--
 6 files changed, 208 insertions(+), 34 deletions(-)

diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/module/AbstractIdPModule.java b/idp-admin-api/src/main/java/net/shibboleth/idp/module/AbstractIdPModule.java
index fc4a87de5..5c1ddef0f 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/module/AbstractIdPModule.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/module/AbstractIdPModule.java
@@ -32,7 +32,9 @@ import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -112,25 +114,48 @@ public abstract class AbstractIdPModule implements IdPModule {
     }
     
     /** {@inheritDoc} */
-    public void enable(@Nonnull final ModuleContext moduleContext) throws ModuleException {
+    @Nonnull @NonnullElements public Map<ModuleResource, ResourceResult> enable(
+            @Nonnull final ModuleContext moduleContext) throws ModuleException {
         if (isHttpClientRequired() && moduleContext.getHttpClient() == null) {
             throw new ModuleException("HTTP client required but not available");
         }
         
         log.debug("Module {} enabling", getId());
-        for (final ModuleResource resource : moduleResources) {
-            ((BasicModuleResource) resource).enable(moduleContext);
+        
+        final Map<ModuleResource,ResourceResult> results;
+        
+        if (!moduleResources.isEmpty()) {
+            results = new HashMap<>(moduleResources.size());
+
+            for (final ModuleResource resource : moduleResources) {
+                results.put(resource, ((BasicModuleResource) resource).enable(moduleContext));
+            }
+        } else {
+            results = Collections.emptyMap();
         }
-        log.info("Module {} enabled", getId());
+        
+        log.debug("Module {} enabled", getId());
+        return results;
     }
 
     /** {@inheritDoc} */
-    public void disable(@Nonnull final ModuleContext moduleContext, final boolean clean) throws ModuleException {
+    @Nonnull @NonnullElements public Map<ModuleResource, ResourceResult> disable(
+            @Nonnull final ModuleContext moduleContext, final boolean clean) throws ModuleException {
         log.debug("Module {} disabling", getId());
-        for (final ModuleResource resource : moduleResources) {
-            ((BasicModuleResource) resource).disable(moduleContext, clean);
+
+        final Map<ModuleResource,ResourceResult> results;
+        
+        if (!moduleResources.isEmpty()) {
+            results = new HashMap<>(moduleResources.size());
+            for (final ModuleResource resource : moduleResources) {
+                results.put(resource, ((BasicModuleResource) resource).disable(moduleContext, clean));
+            }
+        } else {
+            results = Collections.emptyMap();
         }
-        log.info("Module {} disabled", getId());
+        
+        log.debug("Module {} disabled", getId());
+        return results;
     }
     
     /**
@@ -161,6 +186,20 @@ public abstract class AbstractIdPModule implements IdPModule {
             replace = shouldReplace;
         }
 
+        /** {@inheritDoc} */
+        public int hashCode() {
+            return source.hashCode();
+        }
+
+        /** {@inheritDoc} */
+        public boolean equals(final Object obj) {
+            if (obj instanceof ModuleResource) {
+                return source.equals(((ModuleResource) obj).getSource()) &&
+                        destination.equals(((ModuleResource) obj).getDestination());
+            }
+            return false;
+        }
+
         /** {@inheritDoc} */
         @Nonnull public String getSource() {
             return source;
@@ -312,9 +351,11 @@ public abstract class AbstractIdPModule implements IdPModule {
          * 
          * @param moduleContext module context
          * 
+         * @return result of operation
+         * 
          * @throws ModuleException if an error occurs
          */
-        private void enable(@Nonnull final ModuleContext moduleContext) throws ModuleException {
+        @Nonnull private ResourceResult enable(@Nonnull final ModuleContext moduleContext) throws ModuleException {
             log.debug("Module {} enabling resource {}", getId(), source);
 
             final boolean hasChanged = hasChanged(moduleContext);
@@ -325,20 +366,24 @@ public abstract class AbstractIdPModule implements IdPModule {
                 }
 
                 final Path destPath;
+                final ResourceResult result;
                 
                 if (hasChanged) {
                     if (isReplace()) {
                         destPath = moduleContext.getIdPHome().resolve(destination);
                         Files.copy(destPath, destPath.resolveSibling(destPath.getFileName() + ".idpsave"),
                                 StandardCopyOption.REPLACE_EXISTING);
-                        log.info("Module {} preserved {}", getId(), destPath);
+                        log.debug("Module {} preserved {}", getId(), destPath);
+                        result = ResourceResult.REPLACED;
                     } else {
                         final Path basePath = moduleContext.getIdPHome().resolve(destination);
                         destPath = basePath.resolveSibling(basePath.getFileName() + ".idpnew");
+                        result = ResourceResult.ADDED;
                     }
                     
                 } else {
                     destPath = moduleContext.getIdPHome().resolve(destination);
+                    result = ResourceResult.CREATED;
                 }
                 
                 if (!destPath.startsWith(moduleContext.getIdPHome())) {
@@ -347,8 +392,8 @@ public abstract class AbstractIdPModule implements IdPModule {
                 }
 
                 Files.copy(srcStream, destPath, StandardCopyOption.REPLACE_EXISTING);
-                log.info("Module {} created {}", getId(), destPath);
-                
+                log.debug("Module {} created {}", getId(), destPath);
+                return result;
             } catch (final IOException e) {
                 log.error("Module {} unable to enable resource {}", getId(), source);
                 throw new ModuleException(e);
@@ -361,29 +406,38 @@ public abstract class AbstractIdPModule implements IdPModule {
          * @param moduleContext module context
          * @param clean true iff resource should be removed
          * 
+         * @return result of operation
+         * 
          * @throws ModuleException if an error occurs
          */
-        private void disable(@Nonnull final ModuleContext moduleContext, final boolean clean) throws ModuleException {
+        @Nonnull private ResourceResult disable(@Nonnull final ModuleContext moduleContext, final boolean clean)
+                throws ModuleException {
             
+            final ResourceResult result;
             final Path resolved = moduleContext.getIdPHome().resolve(destination);
             log.debug("Module {} resolved resource destination {}", getId(), resolved);
             if (Files.exists(resolved)) {
                 try {
                     if (clean || !hasChanged(moduleContext)) {
-                        log.info("Module {} removing resource {}", getId(), resolved);
+                        log.debug("Module {} removing resource {}", getId(), resolved);
                         Files.delete(resolved);
+                        result = ResourceResult.REMOVED;
                     } else {
-                        log.info("Module {} backing up resource {}", getId(), resolved);
+                        log.debug("Module {} backing up resource {}", getId(), resolved);
                         Files.move(resolved, resolved.resolveSibling(resolved.getFileName() + ".idpsave"),
                                 StandardCopyOption.REPLACE_EXISTING);
+                        result = ResourceResult.SAVED;
                     }
                 } catch (final IOException e) {
                     log.error("Module {} failed to disable {}", getId(), resolved);
                     throw new ModuleException(e);
                 }
             } else {
-                log.info("Module {} resource {} missing, ignoring", getId(), resolved);
+                log.debug("Module {} resource {} missing, ignoring", getId(), resolved);
+                result = ResourceResult.MISSING;
             }
+            
+            return result;
         }
         
     }
diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/module/IdPModule.java b/idp-admin-api/src/main/java/net/shibboleth/idp/module/IdPModule.java
index f072d5a51..0a78b1501 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/module/IdPModule.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/module/IdPModule.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.module;
 import java.net.URL;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -94,9 +95,12 @@ public interface IdPModule extends IdentifiedComponent {
      * 
      * @param moduleContext module context
      * 
+     * @return summary of resource results
+     * 
      * @throws ModuleException if not successful 
      */
-    void enable(@Nonnull final ModuleContext moduleContext) throws ModuleException;
+    @Nonnull @NonnullElements Map<ModuleResource,ResourceResult> enable(@Nonnull final ModuleContext moduleContext)
+            throws ModuleException;
 
     /**
      * Disable the module.
@@ -107,9 +111,12 @@ public interface IdPModule extends IdentifiedComponent {
      * @param clean if true, the module should attempt to fully remove traces of previous
      *  use in a potentially destructive fashion
      * 
+     * @return summary of resource results
+     * 
      * @throws ModuleException if not successful 
      */
-    void disable(@Nonnull final ModuleContext moduleContext, final boolean clean) throws ModuleException;
+    @Nonnull @NonnullElements Map<ModuleResource,ResourceResult> disable(@Nonnull final ModuleContext moduleContext,
+            final boolean clean) throws ModuleException;
     
     /**
      * Interface to a resource managed by the module.
@@ -139,5 +146,26 @@ public interface IdPModule extends IdentifiedComponent {
          */
         public boolean isReplace();
     }
+ 
+    /** Resource management outcome. */
+    public enum ResourceResult {
+        /** Resource was created. */
+        CREATED,
+        
+        /** Resource was created and old resource saved. */
+        REPLACED,
+        
+        /** Resource was created alongside original. */
+        ADDED,
+        
+        /** Resource was removed. */
+        REMOVED,
+        
+        /** Old resource was preserved. */
+        SAVED,
+        
+        /** Resource was missing. */
+        MISSING,
+    };
     
 }
\ No newline at end of file
diff --git a/idp-admin-api/src/main/java/net/shibboleth/idp/module/PropertyDrivenIdPModule.java b/idp-admin-api/src/main/java/net/shibboleth/idp/module/PropertyDrivenIdPModule.java
index 87c9c2416..5cbb7e463 100644
--- a/idp-admin-api/src/main/java/net/shibboleth/idp/module/PropertyDrivenIdPModule.java
+++ b/idp-admin-api/src/main/java/net/shibboleth/idp/module/PropertyDrivenIdPModule.java
@@ -24,6 +24,7 @@ import java.net.URL;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Properties;
 
 import javax.annotation.Nonnull;
@@ -32,6 +33,7 @@ import javax.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
@@ -212,8 +214,9 @@ public class PropertyDrivenIdPModule extends AbstractIdPModule {
 
     /** {@inheritDoc} */
     @Override
-    public void enable(@Nullable final ModuleContext moduleContext) throws ModuleException {
-        super.enable(moduleContext);
+    @Nonnull @NonnullElements public Map<ModuleResource,ResourceResult> enable(
+            @Nullable final ModuleContext moduleContext) throws ModuleException {
+        final Map<ModuleResource,ResourceResult> results = super.enable(moduleContext);
         
         if (moduleContext.getMessageStream() != null) {
             final String msg = moduleProperties.getProperty(getId() + MODULE_POSTENABLE_PROPERTY);
@@ -221,12 +224,15 @@ public class PropertyDrivenIdPModule extends AbstractIdPModule {
                 moduleContext.getMessageStream().println(msg);
             }
         }
+        
+        return results;
     }
 
     /** {@inheritDoc} */
     @Override
-    public void disable(@Nullable final ModuleContext moduleContext, final boolean clean) throws ModuleException {
-        super.disable(moduleContext, clean);
+    @Nonnull @NonnullElements public Map<ModuleResource,ResourceResult> disable(
+            @Nullable final ModuleContext moduleContext, final boolean clean) throws ModuleException {
+        final Map<ModuleResource,ResourceResult> results = super.disable(moduleContext, clean);
 
         if (moduleContext.getMessageStream() != null) {
             final String msg = moduleProperties.getProperty(getId() + MODULE_POSTDISABLE_PROPERTY);
@@ -234,6 +240,8 @@ public class PropertyDrivenIdPModule extends AbstractIdPModule {
                 moduleContext.getMessageStream().println(msg);
             }
         }
+        
+        return results;
     }
 
 }
\ No newline at end of file
diff --git a/idp-admin-impl/.settings/org.eclipse.jdt.core.prefs b/idp-admin-impl/.settings/org.eclipse.jdt.core.prefs
index 7fa508cf8..af08edf91 100644
--- a/idp-admin-impl/.settings/org.eclipse.jdt.core.prefs
+++ b/idp-admin-impl/.settings/org.eclipse.jdt.core.prefs
@@ -132,7 +132,7 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
 org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.release=enabled
 org.eclipse.jdt.core.compiler.source=11
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerArguments.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerArguments.java
index f58d30ca7..3a3ba59f2 100644
--- a/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerArguments.java
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerArguments.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.apache.http.client.HttpClient;
@@ -75,7 +76,7 @@ public class ModuleManagerArguments extends AbstractIdPHomeAwareCommandLineArgum
     @Nullable @NotEmpty private String httpClientSecurityParametersName;
 
     /** {@inheritDoc} */
-    public Logger getLog() {
+    @Nonnull public Logger getLog() {
         if (log == null) {
             log = LoggerFactory.getLogger(ModuleManagerArguments.class);
         }
diff --git a/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerCLI.java b/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerCLI.java
index d87b9c34b..379899636 100644
--- a/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerCLI.java
+++ b/idp-admin-impl/src/main/java/net/shibboleth/idp/module/impl/ModuleManagerCLI.java
@@ -17,7 +17,13 @@
 
 package net.shibboleth.idp.module.impl;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.ServiceLoader;
 
 import javax.annotation.Nonnull;
@@ -31,9 +37,13 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.core.io.Resource;
 
+import com.google.common.base.Strings;
+
 import net.shibboleth.idp.Version;
 import net.shibboleth.idp.cli.AbstractIdPHomeAwareCommandLine;
 import net.shibboleth.idp.module.IdPModule;
+import net.shibboleth.idp.module.IdPModule.ModuleResource;
+import net.shibboleth.idp.module.IdPModule.ResourceResult;
 import net.shibboleth.idp.module.ModuleContext;
 import net.shibboleth.idp.module.ModuleException;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -122,7 +132,7 @@ public final class ModuleManagerCLI extends AbstractIdPHomeAwareCommandLine<Modu
                 doManage(moduleContext, args);
             }
         } catch (final ModuleException e) {
-            System.out.println("FAILED");
+            System.out.println(ANSIColors.ANSI_RED + "[FAILED]" + ANSIColors.ANSI_RESET);
             System.out.println();
             return RC_INIT;
         }
@@ -144,10 +154,24 @@ public final class ModuleManagerCLI extends AbstractIdPHomeAwareCommandLine<Modu
                 System.out.println("\tName: " + module.getName());
                 System.out.println("\tDesc: " + module.getDescription());
                 System.out.println("\tHelp: " + module.getURL());
-                System.out.println("\tStatus: " + (module.isEnabled(moduleContext) ? "ENABLED" : "DISABLED"));
+                if (module.isEnabled(moduleContext)) {
+                    System.out.println("\tStatus: " + ANSIColors.ANSI_GREEN + "ENABLED" + ANSIColors.ANSI_RESET);
+                } else {
+                    System.out.println("\tStatus: " + ANSIColors.ANSI_RED + "DISABLED" + ANSIColors.ANSI_RESET);
+                }
+                final Collection<ModuleResource> resources = module.getResources();
+                resources.forEach(r -> {
+                    System.out.println("\tResource: (" + (r.isReplace() ? "  replace" : "noreplace") + ") " +
+                            r.getDestination());
+                });
             } else {
-                System.out.println("Module: " + module.getId() + ": " +
-                        (module.isEnabled(moduleContext) ? "ENABLED" : "DISABLED"));
+                if (module.isEnabled(moduleContext)) {
+                    System.out.println("Module: " + module.getId() +
+                            ANSIColors.ANSI_GREEN + " [ENABLED]" + ANSIColors.ANSI_RESET);
+                } else {
+                    System.out.println("Module: " + module.getId() +
+                            ANSIColors.ANSI_RED + " [DISABLED]" + ANSIColors.ANSI_RESET);
+                }
             }
             System.out.println();
         }
@@ -164,18 +188,77 @@ public final class ModuleManagerCLI extends AbstractIdPHomeAwareCommandLine<Modu
     private void doManage(@Nonnull final ModuleContext moduleContext, @Nonnull final ModuleManagerArguments args)
             throws ModuleException {
         for (final IdPModule module : ServiceLoader.load(IdPModule.class)) {
+            
+            final boolean enable;
             if (args.getEnableModuleIds().contains(module.getId())) {
-                System.out.print("Enabling " + module.getId() + "...");
-                module.enable(moduleContext);
-                System.out.println("OK");
+                enable = true;
             } else if (args.getDisableModuleIds().contains(module.getId())) {
-                System.out.print("Disabling " + module.getId() + "...");
-                module.disable(moduleContext, args.getClean());
-                System.out.println("OK");
+                enable = false;
+            } else {
+                continue;
+            }
+            
+            try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+                System.out.println((enable ? "Enabling " : "Disabling ") + module.getId() + "...");
+                moduleContext.setMessageStream(new PrintStream(sink));
+                
+                final Map<ModuleResource,ResourceResult> results = enable ? module.enable(moduleContext) :
+                    module.disable(moduleContext, args.getClean());
+                results.forEach(this::doReportOperation);
+                
+                System.out.println(ANSIColors.ANSI_GREEN + "[OK]" + ANSIColors.ANSI_RESET);
+                System.out.println();
+                
+                final String msg = sink.toString(Charset.forName("UTF-8"));
+                moduleContext.setMessageStream(null);
+                if (!Strings.isNullOrEmpty(msg)) {
+                    System.out.println(msg);
+                    System.out.println();
+                }
+                
+            } catch (final IOException e) {
+                getLogger().error("I/O Error", e);
             }
         }
     }
     
+    /**
+     * Report on a resource result.
+     * 
+     * @param resource resource
+     * @param result result of operation
+     */
+    private void doReportOperation(@Nonnull final ModuleResource resource, @Nonnull final ResourceResult result) {
+        System.out.print("\t" + resource.getDestination());
+        switch (result) {
+            case CREATED:
+                System.out.println(" created");
+                break;
+                
+            case REPLACED:
+                System.out.println(" replaced, " + resource.getDestination() + ".idpsave created");
+                break;
+                
+            case ADDED:
+                System.out.println(".idpnew created");
+                break;
+                
+            case REMOVED:
+                System.out.println(" removed");
+                break;
+                
+            case SAVED:
+                System.out.println(" renamed to " + resource.getDestination() + ".idpsave");
+                break;
+                
+            case MISSING:
+                System.out.println(" missing, nothing to do");
+                break;
+                
+            default:
+        }
+    }
+    
 
     /**
      * CLI entry point.

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


More information about the commits mailing list