[spring-extensions] 03/06: Migrate various classes.

Scott Cantor cantor.2 at osu.edu
Wed Jul 6 16:55:15 UTC 2022


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

scantor pushed a commit to branch main
in repository spring-extensions.

View the commit online:
http://git.shibboleth.net/view/?p=spring-extensions.git;a=commit;h=0520245cc70efffc13f26f7c43a107f17132559f

commit 0520245cc70efffc13f26f7c43a107f17132559f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed May 18 13:33:17 2022 -0400

    Migrate various classes.
---
 .../ext/spring/config/ByteArrayToDDFConverter.java |  43 +++
 .../ext/spring/config/DDFToByteArrayConverter.java |  43 +++
 ...actPropertiesApplicationContextInitializer.java | 399 +++++++++++++++++++++
 .../ext/spring/service/LogbackLoggingService.java  | 247 +++++++++++++
 4 files changed, 732 insertions(+)

diff --git a/src/main/java/net/shibboleth/ext/spring/config/ByteArrayToDDFConverter.java b/src/main/java/net/shibboleth/ext/spring/config/ByteArrayToDDFConverter.java
new file mode 100644
index 0000000..803cedf
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/config/ByteArrayToDDFConverter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ext.spring.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.springframework.core.convert.converter.Converter;
+
+import net.shibboleth.utilities.java.support.ddf.DDF;
+
+/**
+ * Spring converter from byte array to {@link DDF} object.
+ * 
+ * @since 7.0.0
+ */
+public class ByteArrayToDDFConverter implements Converter<byte[], DDF> {
+
+    /** {@inheritDoc} */
+    public DDF convert(final byte[] source) {
+        try (final ByteArrayInputStream bais = new ByteArrayInputStream(source)) {
+            return DDF.deserialize(bais);
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/config/DDFToByteArrayConverter.java b/src/main/java/net/shibboleth/ext/spring/config/DDFToByteArrayConverter.java
new file mode 100644
index 0000000..617dc60
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/config/DDFToByteArrayConverter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ext.spring.config;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.springframework.core.convert.converter.Converter;
+import net.shibboleth.utilities.java.support.ddf.DDF;
+
+/**
+ * Spring converter from {@link DDF} to byte array.
+ * 
+ * @since 7.0.0
+ */
+public class DDFToByteArrayConverter implements Converter<DDF, byte[]> {
+
+    /** {@inheritDoc} */
+    public byte[] convert(final DDF source) {
+        try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            source.serialize(baos);
+            return baos.toByteArray();
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/context/AbstractPropertiesApplicationContextInitializer.java b/src/main/java/net/shibboleth/ext/spring/context/AbstractPropertiesApplicationContextInitializer.java
new file mode 100644
index 0000000..cdab6c5
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/context/AbstractPropertiesApplicationContextInitializer.java
@@ -0,0 +1,399 @@
+/*
+ * 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.ext.spring.context;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeSet;
+import java.util.function.BiPredicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * An {@link ApplicationContextInitializer} which appends properties to the application context's environment.
+ * 
+ * <p>Properties are loaded from {@link  #getSearchTarget()} as well as additional property files specified by
+ * {@link #getAdditionalPropertiesPropertyName()} and/or by locating all files under a designated location that
+ * end in ".properties".</p>
+ * 
+ * <p>The {@link #getSearchTarget()} file is searched for in a well-known location returned by
+ * {@link #getSearchLocation()}.</p>
+ * 
+ * <p>If not already set, {@link #getHomePropertyName()} will be set to the first search location in which the
+ * {@link #getSearchTarget()} file is found.</p>
+ * 
+ * <p>A {@link ConstraintViolationException} will be thrown if the property files can not be found or loaded and
+ * {@link #isFailFast(ConfigurableApplicationContext)} returns true.</p>
+ * 
+ * @since 7.0.0
+ */
+public abstract class AbstractPropertiesApplicationContextInitializer
+        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    /** Class logger. */
+    @Nonnull private static final Logger LOG =
+            LoggerFactory.getLogger(AbstractPropertiesApplicationContextInitializer.class);
+
+    /**
+     * Get the name of the property used to identify the "home" of the application's configuration
+     * tree (e.g., "idp.home" or "sp.home").
+     * 
+     * @return home property name
+     */
+    @Nonnull @NotEmpty protected abstract String getHomePropertyName();
+    
+    /**
+     * Get the target resource to be searched for (e.g., "/conf/idp.properties").
+     * 
+     * @return the target resource to be searched for
+     */
+    @Nonnull @NotEmpty protected abstract String getSearchTarget();
+
+    /**
+     * Get the well known search location (e.g., "/opt/shibboleth-idp").
+     * 
+     * @return the well known search locations
+     */
+    @Nonnull @NotEmpty protected abstract String getSearchLocation();
+    
+    /**
+     * Get the name of the property used to control fail-fast behavior (e.g., "idp.initializer.failFast").
+     * 
+     * @return failfast property name
+     */
+    @Nonnull @NotEmpty protected abstract String getFailFastPropertyName();
+    
+    /**
+     * Get the name of the property identifying the additional property resources to load
+     * (e.g., "idp.additionalProperties").
+     * 
+     * @return name of additional properties property
+     */
+    @Nonnull @NotEmpty protected abstract String getAdditionalPropertiesPropertyName();
+
+    /**
+     * Get the name of the property determining whether to enable auto-search
+     * (e.g., "idp.searchForProperties").
+     * 
+     * @return name of additional properties property
+     */
+    @Nonnull @NotEmpty protected abstract String getAutoSearchPropertyName();
+
+    /** {@inheritDoc} */
+    public void initialize(@Nonnull final ConfigurableApplicationContext applicationContext) {
+        LOG.debug("Initializing application context '{}'", applicationContext);
+
+        // TODO: Override default property replacement syntax.
+        // We can't do this now because it would break web.xml's use of ${idp.home}
+        // If we end up breaking web.xml later, I think we could force that in line.
+        // See IDP-1642
+        // applicationContext.getEnvironment().setPlaceholderPrefix("%{");
+        // applicationContext.getEnvironment().setPlaceholderSuffix("}");
+        
+        final String searchLocation = selectSearchLocation(applicationContext);
+        LOG.debug("Attempting to find '{}' at search location '{}'", getSearchTarget(), searchLocation);
+
+        final String searchPath = searchLocation + getSearchTarget();
+
+        LOG.debug("Attempting to find resource '{}'", searchPath);
+        final Resource resource = applicationContext.getResource(searchPath);
+
+        if (resource.exists()) {
+            LOG.debug("Found resource '{}' at search path '{}'", resource, searchPath);
+
+            final Properties properties = loadProperties(null, resource);
+            if (properties == null) {
+                if (isFailFast(applicationContext)) {
+                    LOG.error("Unable to load properties from resource '{}'", resource);
+                    throw new ConstraintViolationException("Unable to load properties from resource");
+                }
+                LOG.warn("Unable to load properties from resource '{}'", resource);
+                return;
+            }
+
+            if ("classpath:".equals(searchLocation) || resource instanceof ClassPathResource) {
+                setHomeProperty(searchLocation, properties);
+            } else {
+                String searchLocationAbsolutePath = Paths.get(searchLocation).toAbsolutePath().toString();
+                // Minimal normalization required on Windows to allow SWF's flow machinery to work.
+                // Just replace backslashes with forward slashes.
+                if (File.separatorChar == '\\') {
+                    searchLocationAbsolutePath = searchLocationAbsolutePath.replace('\\', '/');
+                }
+                setHomeProperty(searchLocationAbsolutePath, properties);
+            }
+
+            loadAdditionalPropertySources(applicationContext, searchLocation, properties);
+
+            logProperties(properties);
+
+            appendPropertySource(applicationContext, resource.toString(), properties);
+
+        } else if (isFailFast(applicationContext)) {
+            LOG.error("Unable to find '{}' at '{}'", getSearchTarget(), searchLocation);
+            throw new ConstraintViolationException(
+                    "Unable to find '" + getSearchTarget() + "' at '" + searchLocation + "'");
+        } else {
+            LOG.warn("Unable to find '{}' at '{}'", getSearchTarget(), searchLocation);
+        }
+    }
+
+    /**
+     * Select the location used to search for the target. Prefers the user-defined search location defined by
+     * {@link #getHomePropertyName()} in the application context. Defaults to the well-known search location
+     * returned from {@link #getSearchLocation()}.
+     * 
+     * @param applicationContext the application context
+     * @return the search location used to search for the target
+     * @throws ConstraintViolationException if the user-defined search location is empty or ends with '/' and
+     *             {@link #isFailFast(ConfigurableApplicationContext)} is true
+     */
+    @Nonnull @NotEmpty protected String selectSearchLocation(
+            @Nonnull final ConfigurableApplicationContext applicationContext) {
+
+        Constraint.isNotNull(applicationContext, "Application context cannot be null");
+        final String homeProperty = applicationContext.getEnvironment().getProperty(getHomePropertyName());
+        if (homeProperty != null && isFailFast(applicationContext)) {
+            Constraint.isNotEmpty(homeProperty, "idp.home cannot be empty");
+            Constraint.isFalse(homeProperty.endsWith("/"), "idp.home cannot end with '/'");
+        }
+        return (homeProperty != null) ? homeProperty : getSearchLocation();
+    }
+
+    /**
+     * Load properties from the resource.
+     * 
+     * @param sink if non-null use this instance as the target
+     * @param resource the resource
+     * @return properties loaded from the resource or {@code  null} if loading failed
+     */
+    @Nullable protected Properties loadProperties(@Nullable final Properties sink, @Nonnull final Resource resource) {
+        Constraint.isNotNull(resource, "Resource cannot be null");
+        try {
+            final Properties holder = new Properties();
+            try (final InputStream is = resource.getInputStream()) {
+                final String filename = resource.getFilename();
+                if (filename != null && filename.endsWith(".xml")) {
+                    holder.loadFromXML(is);
+                } else {
+                    holder.load(is);
+                }
+            }
+            
+            if (sink == null) {
+                return holder;
+            }
+
+            // Check for duplicates before adding.
+            for (final Map.Entry<Object,Object> entry : holder.entrySet()) {
+                if (sink.putIfAbsent(entry.getKey(), entry.getValue()) != null) {
+                    LOG.warn("Ignoring duplicate property '{}'", entry.getKey());
+                }
+            }
+
+            return sink;
+        } catch (final IOException e) {
+            LOG.warn("Unable to load properties from resource '{}'", resource, e);
+            return null;
+        }
+    }
+
+   /**
+     * Find out all the additional property files we need to load.
+     *   
+     * @param searchLocation Where to search from
+     * @param properties the content of idp.properties so far
+     * 
+     * @return a collection of paths
+     */
+    @Nonnull @NonnullElements @NotLive @Unmodifiable protected Collection<String> getAdditionalSources(
+            @Nonnull final String searchLocation, @Nonnull final Properties properties) {
+       final Collection<String> sources = new ArrayList<>();
+       
+       final Boolean autosearch = Boolean.valueOf(properties.getProperty(getAutoSearchPropertyName(), "false"));
+       if (autosearch) {
+           final Path searchRoot = Path.of(searchLocation).resolve("conf");
+           if (searchRoot.toFile().isDirectory()) {
+               final Path registryRoot = searchRoot.resolve("attributes");
+               final String idpPropertiesNative = Path.of(getSearchTarget()).toString();
+               try (final Stream<Path> paths = Files.find(searchRoot, Integer.MAX_VALUE,
+                       new BiPredicate<Path,BasicFileAttributes>() {
+                               public boolean test(final Path path, final BasicFileAttributes u) {
+                                   final String pathAsString = path.toString();
+                                   // convert back and forth to handle different dir separators
+                                   if (u.isRegularFile()
+                                           && path.getFileName().toString().endsWith(".properties")
+                                           && !pathAsString.endsWith(idpPropertiesNative)
+                                           && !pathAsString.startsWith(registryRoot.toString())) {
+                                       LOG.info("Including auto-located properties in {}", path);
+                                       return true;
+                                   }
+                                   return false;
+                               }
+                       }, FileVisitOption.FOLLOW_LINKS)) {
+                   
+                   sources.addAll(paths.map(Path::toString).collect(Collectors.toUnmodifiableList()));
+               } catch (final IOException e) {
+                   LOG.error("Error searching for additional properties", e);
+               }
+           }
+       }
+       
+       final String additionalSources = properties.getProperty(getAdditionalPropertiesPropertyName());
+       if (additionalSources != null) {
+           final String[] split = additionalSources.split(",");
+           for (final String s : split) {
+               final String trimmedSource = StringSupport.trimOrNull(s);
+               if (trimmedSource != null) {
+                   sources.add(searchLocation + trimmedSource);
+               }
+           }
+       }
+       return sources;
+   }
+    
+    /**
+     * Load additional property sources.
+     * 
+     * <p>File names of additional property sources are defined by {@link #getAdditionalPropertiesPropertyName()},
+     * and are resolved relative to the given search location.</p>
+     * 
+     * @param applicationContext the application context
+     * @param searchLocation the location from which additional property sources are resolved
+     * @param properties the properties to be filled with additional property sources
+     * @throws ConstraintViolationException if an error occurs loading the additional property sources and
+     *             {@link #isFailFast(ConfigurableApplicationContext)} is true
+     */
+    protected void loadAdditionalPropertySources(@Nonnull final ConfigurableApplicationContext applicationContext,
+            @Nonnull final String searchLocation, @Nonnull final Properties properties) {
+        
+        for (final String source : getAdditionalSources(searchLocation, properties)) {
+            LOG.debug("Attempting to load properties from resource '{}'", source);
+            final Resource additionalResource = applicationContext.getResource(source);
+            if (additionalResource.exists()) {
+                LOG.debug("Found property resource '{}'", additionalResource);
+                if (loadProperties(properties, additionalResource) == null) {
+                    if (isFailFast(applicationContext)) {
+                        LOG.error("Unable to load properties from resource '{}'", additionalResource);
+                        throw new ConstraintViolationException("Unable to load properties from resource");
+                    }
+                    LOG.warn("Unable to load properties from resource '{}'", additionalResource);
+                    continue;
+                }
+            } else {
+                LOG.warn("Unable to find property resource '{}' (check {}?)", additionalResource,
+                        getAdditionalPropertiesPropertyName());
+            }
+        }
+    }
+    
+    /**
+     * Log property names and values at debug level, suppressing properties whose name matches 'password',
+     * 'credential', 'secret', or 'salt'.
+     * 
+     * @param properties the properties to log
+     */
+    protected void logProperties(@Nonnull final Properties properties) {
+        if (LOG.isDebugEnabled()) {
+            final Pattern pattern = Pattern.compile("password|credential|secret|salt|key", Pattern.CASE_INSENSITIVE);
+            for (final String name : new TreeSet<>(properties.stringPropertyNames())) {
+                final Object value = pattern.matcher(name).find() ? "<suppressed>" : properties.get(name);
+                LOG.debug("Loaded property '{}'='{}'", name, value);
+            }
+        }
+    }
+
+    /**
+     * Add property source to the application context environment with lowest precedence.
+     * 
+     * @param applicationContext the application context
+     * @param name the name of the property source to be added to the application context
+     * @param properties the properties added to the application context
+     */
+    protected void appendPropertySource(@Nonnull final ConfigurableApplicationContext applicationContext,
+            @Nonnull final String name, @Nonnull final Properties properties) {
+        applicationContext.getEnvironment().getPropertySources()
+                .addLast(new PropertiesPropertySource(name, properties));
+    }
+
+    /**
+     * Set the {@link #getHomePropertyName()} property to the given path if not already set.
+     * 
+     * @param path the property value
+     * @param properties the properties
+     */
+    protected void setHomeProperty(@Nonnull final String path, @Nonnull final Properties properties) {
+        Constraint.isNotNull(path, "Path cannot be null");
+        Constraint.isNotNull(properties, "Properties cannot be null");
+
+        if (properties.getProperty(getHomePropertyName()) != null) {
+            LOG.debug("Will not set '{}' property because it is already set.", getHomePropertyName());
+            return;
+        }
+
+        LOG.debug("Setting '{}' property to '{}'", getHomePropertyName(), path);
+
+        properties.setProperty(getHomePropertyName(), path);
+    }
+
+    /**
+     * Whether we fail immediately if the config is bogus. Defaults to true. Controlled by the value of the
+     * {@link #getFailFastPropertyName()}.
+     * <b>This functionality is reserved for use in tests </b> where is is usually used to allow
+     * tests to be run in the presence of partial configuration.
+     * 
+     * @param applicationContext the application context
+     * @return whether we fail immediately if the config is faulty or incomplete.
+     */
+    protected boolean isFailFast(@Nonnull final ConfigurableApplicationContext applicationContext) {
+        Constraint.isNotNull(applicationContext, "Application context cannot be null");
+        final String failFast = applicationContext.getEnvironment().getProperty(getFailFastPropertyName());
+        return (failFast == null) ? true : Boolean.parseBoolean(failFast);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/service/LogbackLoggingService.java b/src/main/java/net/shibboleth/ext/spring/service/LogbackLoggingService.java
new file mode 100644
index 0000000..c2c5ca9
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/service/LogbackLoggingService.java
@@ -0,0 +1,247 @@
+/*
+ * 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.ext.spring.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import com.google.common.io.Closeables;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.status.ErrorStatus;
+import ch.qos.logback.core.status.InfoStatus;
+import ch.qos.logback.core.status.StatusManager;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.resource.Resource;
+import net.shibboleth.utilities.java.support.service.AbstractReloadableService;
+import net.shibboleth.utilities.java.support.service.LoggingService;
+import net.shibboleth.utilities.java.support.service.ServiceException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+/**
+ * Simple {@link LoggingService} that watches for logback configuration file changes
+ * and reloads the file when a change occurs.
+ * 
+ * @since 7.0.0
+ */
+public class LogbackLoggingService extends AbstractReloadableService<Object>
+        implements LoggingService, ApplicationContextAware {
+    
+    /** Logback logger context. */
+    @NonnullAfterInit private LoggerContext loggerContext;
+
+    /** Logger used to log messages without relying on the logging system to be full initialized. */
+    @NonnullAfterInit private StatusManager statusManager;
+
+    /** URL to the fallback logback configuration found in the IdP jar. */
+    @NonnullAfterInit private Resource fallbackConfiguration;
+
+    /** Logging configuration resource. */
+    @NonnullAfterInit private Resource configurationResource;
+    
+    /** Optional name of a property to populate into the {@link LoggerContext}. */
+    @Nullable @NotEmpty private String homePropertyName;
+
+    /** Spring application context. */
+    @Nullable private ApplicationContext applicationContext;
+
+    /** {@inheritDoc} */
+    public void setLoggingConfiguration(@Nonnull final Resource configuration) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        configurationResource = Constraint.isNotNull(configuration, "Logging configuration resource cannot be null");
+    }
+    
+    /**
+     * Set a resource to use as a fallback configuration if the primary is unavailable.
+     * 
+     * @param fallback fallback configuration resouurce
+     */
+    public void setFallbackConfiguration(@Nonnull final Resource fallback) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        fallbackConfiguration = Constraint.isNotNull(fallback, "Logging configuration falback resource cannot be null");
+    }
+    
+    /**
+     * Set name of a property to load into {@link LoggerContext}.
+     * 
+     * @param name property name
+     */
+    public void setHomePropertyName(@Nullable @NotEmpty final String name) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        homePropertyName = StringSupport.trimOrNull(name);
+    }
+
+    /** {@inheritDoc} */
+    public void setApplicationContext(final ApplicationContext context) {
+        applicationContext = context;
+    }
+
+    /**
+     * {@inheritDoc}.
+     * 
+     * This service does not support a ServiceableComponent, so return null.
+     */
+    @Override
+    @Nullable public ServiceableComponent<Object> getServiceableComponent() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        if (configurationResource == null) {
+            throw new ComponentInitializationException("Logging configuration must be specified.");
+        }
+    
+        loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        statusManager = loggerContext.getStatusManager();
+        if (!fallbackConfiguration.exists()) {
+            if (isFailFast()) {
+                throw new ComponentInitializationException(getLogPrefix() + "Cannot locate fallback configuration");
+            }
+            statusManager.add(new ErrorStatus("Cannot locate fallback configuration at "
+                    + fallbackConfiguration.getDescription(), this));
+        }
+        super.doInitialize();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected synchronized boolean shouldReload() {
+        try {
+            final Instant lastReload = getLastSuccessfulReloadInstant();
+            if (null == lastReload) {
+                return true;
+            }
+            return configurationResource.lastModified() > lastReload.toEpochMilli();
+        } catch (final IOException e) {
+            statusManager.add(new ErrorStatus(
+                    "Error checking last modified time of logging service configuration resource "
+                            + configurationResource.getDescription(), this, e));
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected synchronized void doReload() {
+        loadLoggingConfiguration();
+    }
+
+    /**
+     * Reads and loads in a new logging configuration.
+     * 
+     * @throws ServiceException thrown if there is a problem loading the logging configuration
+     */
+    protected void loadLoggingConfiguration() {
+        InputStream ins = null;
+        try {
+            statusManager.add(new InfoStatus("Loading new logging configuration resource: "
+                    + configurationResource.getDescription(), this));
+            ins = configurationResource.getInputStream();
+            loadLoggingConfiguration(ins);
+        } catch (final Exception e) {
+            try {
+                Closeables.close(ins, true);
+            } catch (final IOException e1) {
+                // swallowed && logged by Closeables but...
+                throw new ServiceException(e1);
+            }
+            statusManager.add(new ErrorStatus("Error loading logging configuration file: "
+                    + configurationResource.getDescription(), this, e));
+            try {
+                statusManager.add(new InfoStatus("Loading fallback logging configuration", this));
+                ins = fallbackConfiguration.getInputStream();
+                loadLoggingConfiguration(ins);
+            } catch (final IOException ioe) {
+                try {
+                    Closeables.close(ins, true);
+                } catch (final IOException e1) {
+                    // swallowed && logged by Closeables
+                    throw new ServiceException(e1);
+                }
+                statusManager.add(new ErrorStatus("Error loading fallback logging configuration", this, e));
+                throw new ServiceException("Unable to load fallback logging configuration");
+            }
+        } finally {
+            try {
+                Closeables.close(ins, true);
+            } catch (final IOException e) {
+                // swallowed && logged by Closeables
+                throw new ServiceException(e);
+            }
+        }
+    }
+
+    /**
+     * Loads a logging configuration in to the active logger context. Error messages are printed out to the status
+     * manager.
+     * 
+     * @param loggingConfig logging configuration file
+     * 
+     * @throws ServiceException thrown is there is a problem loading the logging configuration
+     */
+    protected void loadLoggingConfiguration(final InputStream loggingConfig) {
+        try {
+            loggerContext.reset();
+            loadHomeProperty();
+            final JoranConfigurator configurator = new JoranConfigurator();
+            configurator.setContext(loggerContext);
+            configurator.doConfigure(loggingConfig);
+            loggerContext.start();
+        } catch (final JoranException e) {
+            throw new ServiceException(e);
+        }
+    }
+
+    /**
+     * Add the "home" property from the Spring application context to the logger context.
+     * 
+     * <p>This is principally to allow a defaulted value of this property to be referenced within
+     * the logging configuration itself as a location.</p>
+     */
+    protected void loadHomeProperty() {
+        if (applicationContext != null && homePropertyName != null) {
+            final String home = applicationContext.getEnvironment().getProperty(homePropertyName);
+            if (home != null) {
+                statusManager.add(
+                        new InfoStatus("Setting logger property '" + homePropertyName + "' to '" + home + "'", this));
+                loggerContext.putProperty(homePropertyName, home);
+            }
+        }
+    }
+    
+}
\ No newline at end of file

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


More information about the commits mailing list