/*
* Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
*
* Licensed 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 org.opensaml.saml2.metadata.provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.namespace.QName;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.RoleDescriptor;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A metadata provider that uses registered providers, in turn, to answer queries.
*
* When searching for entity specific information (entity metadata, roles, etc.) the entity descriptor used is the first
* non-null descriptor found while iterating over the registered providers in insertion order.
*
* This chaining provider implements observation by registering an observer with each contained provider. When the
* contained provider emits a change this provider will also emit a change to observers registered with it. As such,
* developers should be careful not to register a the same observer with both container providers and this provider.
* Doing so will result in an observer being notified twice for each change.
*/
public class ChainingMetadataProvider extends BaseMetadataProvider implements ObservableMetadataProvider {
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(ChainingMetadataProvider.class);
/** List of registered observers. */
private ArrayList<Observer> observers;
/** Registred providers. */
private ArrayList<MetadataProvider> providers;
/** Lock used to block reads during write and vice versa. */
private ReadWriteLock providerLock;
/** Constructor. */
public ChainingMetadataProvider() {
super();
observers = new ArrayList<Observer>();
providers = new ArrayList<MetadataProvider>();
providerLock = new ReentrantReadWriteLock(true);
}
/**
* Gets an immutable the list of currently registered providers.
*
* @return list of currently registered providers
*/
public List<MetadataProvider> getProviders() {
return Collections.unmodifiableList(providers);
}
/**
* Replaces the current set of metadata providers with give collection.
*
* @param newProviders the metadata providers to replace the current providers with
*
* @throws MetadataProviderException thrown if there is a problem adding the metadata provider
*/
public void setProviders(List<MetadataProvider> newProviders) throws MetadataProviderException {
providers.clear();
for (MetadataProvider provider : newProviders) {
addMetadataProvider(provider);
}
}
/**
* Adds a metadata provider to the list of registered providers.
*
* @param newProvider the provider to be added
*
* @throws MetadataProviderException thrown if there is a problem adding the metadata provider
*/
public void addMetadataProvider(MetadataProvider newProvider) throws MetadataProviderException {
if (newProvider != null) {
newProvider.setRequireValidMetadata(requireValidMetadata());
newProvider.setMetadataFilter(getMetadataFilter());
if (newProvider instanceof ObservableMetadataProvider) {
((ObservableMetadataProvider) newProvider).getObservers().add(new ContainedProviderObserver());
}
providers.add(newProvider);
}
}
/**
* Removes a metadata provider from the list of registered providers.
*
* @param provider provider to be removed
*/
public void removeMetadataProvider(MetadataProvider provider) {
providers.remove(provider);
}
/** {@inheritDoc} */
public void setRequireValidMetadata(boolean requireValidMetadata) {
super.setRequireValidMetadata(requireValidMetadata);
Lock writeLock = providerLock.writeLock();
writeLock.lock();
for (MetadataProvider provider : providers) {
provider.setRequireValidMetadata(requireValidMetadata);
}
writeLock.unlock();
}
/** {@inheritDoc} */
public void setMetadataFilter(MetadataFilter newFilter) throws MetadataProviderException {
super.setMetadataFilter(newFilter);
Lock writeLock = providerLock.writeLock();
writeLock.lock();
try {
for (MetadataProvider provider : providers) {
provider.setMetadataFilter(newFilter);
}
} catch (MetadataProviderException e) {
throw e;
} finally {
writeLock.unlock();
}
}
/**
* Gets the metadata from every registered provider and places each within a newly created EntitiesDescriptor.
*
* {@inheritDoc}
*/
public XMLObject getMetadata() throws MetadataProviderException {
XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
SAMLObjectBuilder<EntitiesDescriptor> builder = (SAMLObjectBuilder<EntitiesDescriptor>) builderFactory
.getBuilder(EntitiesDescriptor.DEFAULT_ELEMENT_NAME);
EntitiesDescriptor metadataRoot = builder.buildObject();
Lock readLock = providerLock.readLock();
readLock.lock();
XMLObject providerMetadata;
try {
for (MetadataProvider provider : providers) {
providerMetadata = provider.getMetadata();
if (providerMetadata instanceof EntitiesDescriptor) {
metadataRoot.getEntitiesDescriptors().add((EntitiesDescriptor) providerMetadata);
} else if (providerMetadata instanceof EntityDescriptor) {
metadataRoot.getEntityDescriptors().add((EntityDescriptor) providerMetadata);
}
}
} catch (MetadataProviderException e) {
throw e;
} finally {
readLock.unlock();
}
return metadataRoot;
}
/** {@inheritDoc} */
public EntitiesDescriptor getEntitiesDescriptor(String name) throws MetadataProviderException {
Lock readLock = providerLock.readLock();
readLock.lock();
EntitiesDescriptor descriptor = null;
try {
for (MetadataProvider provider : providers) {
log.debug("Checking child metadata provider for entities descriptor with name: {}", name);
descriptor = provider.getEntitiesDescriptor(name);
if (descriptor != null) {
break;
}
}
} catch (MetadataProviderException e) {
throw e;
} finally {
readLock.unlock();
}
return descriptor;
}
/** {@inheritDoc} */
public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException {
Lock readLock = providerLock.readLock();
readLock.lock();
EntityDescriptor descriptor = null;
try {
for (MetadataProvider provider : providers) {
log.debug("Checking child metadata provider for entity descriptor with entity ID: {}", entityID);
descriptor = provider.getEntityDescriptor(entityID);
if (descriptor != null) {
break;
}
}
} catch (MetadataProviderException e) {
throw e;
} finally {
readLock.unlock();
}
return descriptor;
}
/** {@inheritDoc} */
public List<RoleDescriptor> getRole(String entityID, QName roleName) throws MetadataProviderException {
EntityDescriptor entityMetadata = getEntityDescriptor(entityID);
if (entityMetadata == null) {
return null;
}
return entityMetadata.getRoleDescriptors(roleName);
}
/** {@inheritDoc} */
public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol)
throws MetadataProviderException {
EntityDescriptor entityMetadata = getEntityDescriptor(entityID);
if (entityMetadata == null) {
return null;
}
List<RoleDescriptor> roles = entityMetadata.getRoleDescriptors(roleName, supportedProtocol);
if (roles != null && !roles.isEmpty()) {
return roles.get(0);
}
return null;
}
/** {@inheritDoc} */
public List<Observer> getObservers() {
return observers;
}
/**
* Convenience method for calling
* {@link org.opensaml.saml2.metadata.provider.ObservableMetadataProvider.Observer#onEvent(MetadataProvider)} on
* every registered Observer passing in this provider.
*/
protected void emitChangeEvent() {
synchronized (observers) {
for (Observer observer : observers) {
if (observer != null) {
observer.onEvent(this);
}
}
}
}
/**
* Observer that clears the descriptor index of this provider.
*/
private class ContainedProviderObserver implements Observer {
/** {@inheritDoc} */
public void onEvent(MetadataProvider provider) {
emitChangeEvent();
}
}
}