net.shibboleth.ext.spring.service.ReloadableSpringService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-extensions Show documentation
Show all versions of spring-extensions Show documentation
A set of extensions for the Spring framework. This package includes things like
factory beans, converters, etc.
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import net.shibboleth.ext.spring.util.SpringSupport;
import net.shibboleth.utilities.java.support.annotation.ParameterName;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
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.service.AbstractReloadableService;
import net.shibboleth.utilities.java.support.service.ServiceException;
import net.shibboleth.utilities.java.support.service.ServiceableComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.Lifecycle;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* This class provides a reloading interface to a {@link ServiceableComponent} via Spring.
* This class extends {@link org.springframework.context.Lifecycle}. and thus
* It acts as the bridge between this interface and
* {@link net.shibboleth.utilities.java.support.component.InitializableComponent} and
* {@link net.shibboleth.utilities.java.support.component.DestructableComponent}
*
* @param The precise service being implemented.
*/
@ThreadSafe
public class ReloadableSpringService extends AbstractReloadableService implements ApplicationContextAware,
BeanNameAware, Lifecycle {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(ReloadableSpringService.class);
/** List of configuration resources for this service. */
@Nullable @NonnullElements private List serviceConfigurations;
/** List of bean factory post processors for this service's content. */
@Nonnull @NonnullElements private List factoryPostProcessors;
/** List of bean post processors for this service's content. */
@Nonnull @NonnullElements private List postProcessors;
/** The class we are looking for. */
@Nonnull private final Class theClaz;
/** How to summon up the {@link ServiceableComponent} from the {@link ApplicationContext}. */
@Nonnull private final Function serviceStrategy;
/** Application context owning this engine. */
@Nullable private ApplicationContext parentContext;
/** The bean name. */
@Nullable private String beanName;
/** The last known good component. */
@Nullable private ServiceableComponent cachedComponent;
/** Did the last load fail? An optimization only. */
private boolean lastLoadFailed = true;
/**
* Time, in milliseconds, when the service configuration for the given index was last observed to have changed. -1
* indicates the configuration resource did not exist.
*/
@Nullable private long[] resourceLastModifiedTimes;
/**
* Constructor.
*
* @param claz The interface being implemented.
*/
public ReloadableSpringService(@Nonnull @ParameterName(name="claz") final Class claz) {
this(claz, new ClassBasedServiceStrategy());
}
/**
* Constructor.
*
* @param claz The interface being implemented.
* @param strategy the strategy to use to look up servicable component to look for.
*/
public ReloadableSpringService(@Nonnull @ParameterName(name="claz") final Class claz,
@Nonnull @ParameterName(name="strategy")
final Function strategy) {
theClaz = Constraint.isNotNull(claz, "Class cannot be null");
serviceStrategy = Constraint.isNotNull(strategy, "Strategy cannot be null");
factoryPostProcessors = Collections.emptyList();
postProcessors = Collections.emptyList();
}
/**
* Gets the application context that is the parent to this service's context.
*
* @return application context that is the parent to this service's context
*/
@Nullable public ApplicationContext getParentContext() {
return parentContext;
}
/**
* Sets the application context that is the parent to this service's context.
*
* This setting can not be changed after the service has been initialized.
*
* @param context context that is the parent to this service's context, may be null
*/
public void setParentContext(@Nullable final ApplicationContext context) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
parentContext = context;
}
/**
* Gets an unmodifiable list of configurations for this service.
*
* @return unmodifiable list of configurations for this service
*/
@Nonnull public List getServiceConfigurations() {
return serviceConfigurations;
}
/**
* Sets the list of configurations for this service.
*
* This setting can not be changed after the service has been initialized.
*
* @param configs list of configurations for this service, may be null or empty
*/
public void setServiceConfigurations(@Nonnull @NonnullElements final List configs) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
serviceConfigurations =
ImmutableList. builder().addAll(Iterables.filter(configs, Predicates.notNull())).build();
if (!serviceConfigurations.isEmpty()) {
resourceLastModifiedTimes = new long[serviceConfigurations.size()];
final int numOfResources = serviceConfigurations.size();
Resource serviceConfig;
for (int i = 0; i < numOfResources; i++) {
serviceConfig = serviceConfigurations.get(i);
try {
if (serviceConfig.exists()) {
resourceLastModifiedTimes[i] = serviceConfig.lastModified();
} else {
resourceLastModifiedTimes[i] = -1;
}
} catch (final IOException e) {
log.info("{} Configuration resource '" + serviceConfig.getDescription()
+ "' last modification date could not be determined", getLogPrefix(), e);
resourceLastModifiedTimes[i] = -1;
}
}
} else {
resourceLastModifiedTimes = null;
}
}
/** Set the strategy by which the Service can locate the resources it needs to know about.
*
Not implemented
* @param strategy the way to get the resources. Precise details are tbd.
*/
public void setServiceConfigurationStrategy(@Nonnull final Function, List> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
throw new UnsupportedOperationException("This UnsupportedOperationException method has not been implemented");
}
/**
* Set the list of bean factory post processors for this service.
*
* @param processors bean factory post processors to apply
*/
public void setBeanFactoryPostProcessors(
@Nonnull @NonnullElements final List processors) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
factoryPostProcessors = new ArrayList<>(Collections2.filter(processors, Predicates.notNull()));
}
/**
* Set the list of bean post processors for this service.
*
* @param processors bean post processors to apply
*/
public void setBeanPostProcessors(@Nonnull @NonnullElements final List processors) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
postProcessors = new ArrayList<>(Collections2.filter(processors, Predicates.notNull()));
}
/** {@inheritDoc} */
@Override
public final void start() {
try {
initialize();
} catch (final ComponentInitializationException e) {
throw new BeanInitializationException("Could not start service", e);
}
}
/** {@inheritDoc} */
@Override
public final void stop() {
destroy();
}
/** {@inheritDoc}. */
@Override
public boolean isRunning() {
return isInitialized() && !isDestroyed();
}
/** {@inheritDoc} */
// Checkstyle: CyclomaticComplexity OFF
@Override protected boolean shouldReload() {
// Loop over each resource and check if the any resources have been changed since
// the last time the service was reloaded. I believe a read lock is all we need here
// to allow use of the service to proceed while we check on the state. Actual reloading
// requires the write lock, and the only post-initialization code that reads or writes
// the array of resource mod-time data is this code, which is on one thread.
if (resourceLastModifiedTimes == null) {
return false;
}
if (lastLoadFailed) {
return true;
}
boolean configResourceChanged = false;
final int numOfResources = serviceConfigurations.size();
Resource serviceConfig;
long serviceConfigLastModified;
for (int i = 0; i < numOfResources; i++) {
serviceConfig = serviceConfigurations.get(i);
try {
if (resourceLastModifiedTimes[i] == -1 && !serviceConfig.exists()) {
// Resource did not exist and still does not exist.
log.debug("{} Resource remains unavailable/inaccessible: '{}'", getLogPrefix(),
serviceConfig.getDescription());
} else if (resourceLastModifiedTimes[i] == -1 && serviceConfig.exists()) {
// Resource did not exist, but does now.
log.debug("{} Resource was unavailable, now present: '{}'", getLogPrefix(),
serviceConfig.getDescription());
configResourceChanged = true;
resourceLastModifiedTimes[i] = serviceConfig.lastModified();
} else if (resourceLastModifiedTimes[i] > -1 && !serviceConfig.exists()) {
// Resource existed, but is now unavailable.
log.debug("{} Resource was available, now is not: '{}'", getLogPrefix(),
serviceConfig.getDescription());
configResourceChanged = true;
resourceLastModifiedTimes[i] = -1;
} else {
// Check to see if an existing resource, that still exists, has been modified.
serviceConfigLastModified = serviceConfig.lastModified();
if (serviceConfigLastModified != resourceLastModifiedTimes[i]) {
log.debug("{} Resource has changed: '{}'", getLogPrefix(), serviceConfig.getDescription());
configResourceChanged = true;
resourceLastModifiedTimes[i] = serviceConfigLastModified;
} else {
log.trace("{} Resource has not changed '{}'", getLogPrefix(), serviceConfig.getDescription());
}
}
} catch (final IOException e) {
log.info("{} Configuration resource '{}' last modification date could not be determined",
getLogPrefix(), serviceConfig.getDescription(), e);
configResourceChanged = true;
}
}
return configResourceChanged;
}
// Checkstyle: CyclomaticComplexity ON
/** {@inheritDoc} */
@Override protected void doReload() {
super.doReload();
log.debug("{} Creating new ApplicationContext for service '{}'", getLogPrefix(), getId());
log.debug("{} Reloading from {}", getLogPrefix(), getServiceConfigurations());
final GenericApplicationContext appContext;
try {
appContext =
SpringSupport.newContext(getId(), getServiceConfigurations(), factoryPostProcessors, postProcessors,
Collections. emptyList(), getParentContext());
} catch (final FatalBeanException e) {
throw new ServiceException(e);
}
log.debug("{} New Application Context created for service '{}'", getLogPrefix(), getId());
final ServiceableComponent service;
try {
service = serviceStrategy.apply(appContext);
} catch (final ServiceException e) {
appContext.close();
throw new ServiceException("Failed to load " + getServiceConfigurations(), e);
}
service.pinComponent();
// Now check it's the right type before we continue.
final T theComponent = service.getComponent();
log.debug("{} Testing that {} is a superclass of {}", getLogPrefix(), theComponent.getClass(), theClaz);
if (!theClaz.isAssignableFrom(theComponent.getClass())) {
//
// tear it down
//
service.unpinComponent();
service.unloadComponent();
throw new ServiceException("Class was not the same or a superclass of configured class");
}
// Otherwise we are ready to swap in the new component; so only
// now do we grab the lock.
//
// Note that we are grabbing the lock on the component before the lock on this
// object, which would be an inversion with the getServiceableComponent ranking
// except the component will never be seen before we drop the lock and so
// there can be no inversion
//
final ServiceableComponent oldComponent;
synchronized (this) {
oldComponent = cachedComponent;
cachedComponent = service;
service.unpinComponent();
}
log.info("{} Completed reload and swapped in latest configuration for service '{}'", getLogPrefix(), getId());
if (null != oldComponent) {
log.debug("{} Unloading previous configuration for service '{}'", getLogPrefix(), getId());
oldComponent.unloadComponent();
}
lastLoadFailed = false;
log.info("{} Reload complete", getLogPrefix());
}
/** {@inheritDoc} */
@Override protected void doDestroy() {
final ServiceableComponent oldComponent = cachedComponent;
cachedComponent = null;
// And tear down. Note that we are synchronized on this right now
// and this will grab the lock - but that is OK because the ranking
// is to lock this object, then the ServicableComponent.
if (null != oldComponent) {
oldComponent.unloadComponent();
}
super.doDestroy();
}
/**
* Get the serviceable component. We do this under interlock and grab the lock on the component.
*
* @return the pinned component.
*/
@Override public synchronized ServiceableComponent getServiceableComponent() {
if (null == cachedComponent) {
return null;
}
cachedComponent.pinComponent();
return cachedComponent;
}
/** {@inheritDoc} */
@Override public void setApplicationContext(final ApplicationContext applicationContext) {
setParentContext(applicationContext);
}
/** {@inheritDoc} */
@Override public void setBeanName(@Nonnull @NotEmpty final String name) {
beanName = name;
}
/** {@inheritDoc} */
@Override protected void doInitialize() throws ComponentInitializationException {
if (getId() == null) {
setId(beanName);
}
super.doInitialize();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy