net.shibboleth.idp.attribute.resolver.impl.ad.TemplateAttributeDefinition.java Source code

Java tutorial

Introduction

Here is the source code for net.shibboleth.idp.attribute.resolver.impl.ad.TemplateAttributeDefinition.java

Source

/*
 * 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.attribute.resolver.impl.ad;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

import net.shibboleth.idp.attribute.IdPAttribute;
import net.shibboleth.idp.attribute.IdPAttributeValue;
import net.shibboleth.idp.attribute.StringAttributeValue;
import net.shibboleth.idp.attribute.UnsupportedAttributeTypeException;
import net.shibboleth.idp.attribute.resolver.AbstractAttributeDefinition;
import net.shibboleth.idp.attribute.resolver.PluginDependencySupport;
import net.shibboleth.idp.attribute.resolver.ResolutionException;
import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NullableElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
import net.shibboleth.utilities.java.support.collection.CollectionSupport;
import net.shibboleth.utilities.java.support.collection.LazyMap;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
import net.shibboleth.utilities.java.support.velocity.Template;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicates;

/**
 * An attribute definition that constructs its values based on the values of its dependencies using the Velocity
 * Template Language. Dependencies may have multiple values, however multiples dependencies must have the same number of
 * values. In the case of multi-valued dependencies, the template will be evaluated multiples times, iterating over each
 * dependency. <br/>
 * The template is inserted into the engine with a unique name derived from this class and from the id supplied for this
 * attribute. <br/>
 * This is marked not thread safe since the constructor cannot do a safe check & insert of the template into the
 * repository.
 */
@NotThreadSafe
public class TemplateAttributeDefinition extends AbstractAttributeDefinition {

    /** Class logger. */
    @Nonnull
    private final Logger log = LoggerFactory.getLogger(TemplateAttributeDefinition.class);

    /** Template to be evaluated. */
    @NonnullAfterInit
    private Template template;

    /** Template (as Text) to be evaluated. */
    @NonnullAfterInit
    private String templateText;

    /** VelocityEngine. */
    @NonnullAfterInit
    private VelocityEngine engine;

    /** The names of the attributes we need. */
    @Nonnull
    private List<String> sourceAttributes = Collections.emptyList();

    /**
     * Get the source attribute IDs.
     * 
     * @return the source attribute IDs
     */
    @Nonnull
    @Unmodifiable
    @NonnullElements
    public List<String> getSourceAttributes() {
        return Collections.unmodifiableList(sourceAttributes);
    }

    /**
     * Set the source attribute IDs.
     * 
     * @param newSourceAttributes the source attribute IDs
     */
    public void setSourceAttributes(@Nonnull @NullableElements final List<String> newSourceAttributes) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        final ArrayList<String> checkedSourceAttrs = new ArrayList<String>(newSourceAttributes.size());
        CollectionSupport.addIf(checkedSourceAttrs, newSourceAttributes, Predicates.notNull());
        sourceAttributes = Collections.unmodifiableList(checkedSourceAttrs);
    }

    /**
     * Gets the template text to be evaluated.
     * 
     * @return the template
     */
    @NonnullAfterInit
    public Template getTemplate() {
        return template;
    }

    /**
     * Gets the template text to be evaluated.
     * 
     * @return the template
     */
    @NonnullAfterInit
    public String getTemplateText() {
        return templateText;
    }

    /**
     * Sets the template to be evaluated.
     * 
     * @param velocityTemplate template to be evaluated
     */
    public synchronized void setTemplateText(@Nullable final String velocityTemplate) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        templateText = velocityTemplate;
    }

    /**
     * Gets the {@link VelocityEngine} to be used.
     * 
     * @return the template
     */
    @NonnullAfterInit
    public VelocityEngine getVelocityEngine() {
        return engine;
    }

    /**
     * Sets the {@link VelocityEngine} to be used.
     * 
     * @param velocityEngine engine to be used
     */
    public synchronized void setVelocityEngine(@Nullable final VelocityEngine velocityEngine) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        engine = velocityEngine;
    }

    /** {@inheritDoc} */
    @Override
    @Nonnull
    protected IdPAttribute doAttributeDefinitionResolve(@Nonnull final AttributeResolutionContext resolutionContext,
            @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {

        final IdPAttribute resultantAttribute = new IdPAttribute(getId());

        final Map<String, Iterator<IdPAttributeValue<?>>> sourceValues = new LazyMap<>();
        final int valueCount = setupSourceValues(workContext, sourceValues);

        // build velocity context
        VelocityContext velocityContext;
        String templateResult;
        final LinkedHashSet<StringAttributeValue> hs = new LinkedHashSet<>(valueCount);

        for (int i = 0; i < valueCount; i++) {
            log.debug("{} determing value {}", getLogPrefix(), i + 1);
            velocityContext = new VelocityContext();

            for (final String attributeId : sourceValues.keySet()) {
                final IdPAttributeValue value = sourceValues.get(attributeId).next();
                if (!(value instanceof StringAttributeValue)) {
                    throw new ResolutionException(new UnsupportedAttributeTypeException(
                            getLogPrefix() + "This attribute definition only supports attribute value types of "
                                    + StringAttributeValue.class.getName() + " not values of type "
                                    + value.getClass().getName()));
                }

                log.debug("{} adding value '{}' for attribute '{}' to the template context",
                        new Object[] { getLogPrefix(), value.getValue(), attributeId, });
                velocityContext.put(attributeId, value.getValue());
            }

            try {
                log.debug("{} evaluating template", getLogPrefix());
                templateResult = template.merge(velocityContext);
                log.debug("{} result of template evaluating was '{}'", getLogPrefix(), templateResult);
                hs.add(new StringAttributeValue(templateResult));
            } catch (final VelocityException e) {
                // uncovered path
                log.error(getLogPrefix() + " unable to evaluate velocity template", e);
                throw new ResolutionException("Unable to evaluate template", e);
            }
        }
        resultantAttribute.setValues(hs);

        return resultantAttribute;
    }

    /** {@inheritDoc} */
    @Override
    protected void doInitialize() throws ComponentInitializationException {
        super.doInitialize();

        if (getDependencies().isEmpty()) {
            throw new ComponentInitializationException(getLogPrefix() + " no dependencies were configured");
        }

        if (null == engine) {
            throw new ComponentInitializationException(getLogPrefix() + " no velocity engine was configured");
        }

        if (sourceAttributes.isEmpty()) {
            log.info("{} no Source Attributes supplied, was this intended?", getLogPrefix());
        }

        templateText = StringSupport.trimOrNull(templateText);

        if (null == templateText) {
            // V2 compatibility - define our own template
            final StringBuffer defaultTemplate = new StringBuffer();
            for (final String id : sourceAttributes) {
                defaultTemplate.append("${").append(id).append("} ");
            }
            if (defaultTemplate.length() > 0) {
                templateText = defaultTemplate.toString();
            } else {
                throw new ComponentInitializationException(
                        getLogPrefix() + " no template and no source attributes were configured");
            }
            log.info("{} no template supplied. Default generated was '{}'", getLogPrefix(), templateText);
        }

        template = Template.fromTemplate(engine, templateText);
    }

    /**
     * Set up a map which can be used to populate the template. The key is the attribute name and the value is the
     * iterator to give all the names. We also return how deep the iteration will be and throw an exception if there is
     * a mismatch in number of elements in any attribute. <br/>
     * Finally, the names of the source attributes is checked against the dependency attributes and if there is a
     * mismatch then a warning is emitted.
     * 
     * @param workContext source for dependencies
     * @param sourceValues to populate with the attribute iterators
     * 
     * @return how many values in the attributes
     * @throws ResolutionException if there is a mismatched count of attributes
     */
    private int setupSourceValues(@Nonnull final AttributeResolverWorkContext workContext,
            final Map<String, Iterator<IdPAttributeValue<?>>> sourceValues) throws ResolutionException {

        final Map<String, Set<IdPAttributeValue<?>>> dependencyAttributes = PluginDependencySupport
                .getAllAttributeValues(workContext, getDependencies());

        int valueCount = 0;
        boolean valueCountSet = false;

        for (final String attributeName : sourceAttributes) {

            final Set<IdPAttributeValue<?>> attributeValues = dependencyAttributes.get(attributeName);
            if (null == attributeValues) {
                throw new ResolutionException(
                        getLogPrefix() + " no values found for attribute named '" + attributeName + "'");
            }

            if (!valueCountSet) {
                valueCount = attributeValues.size();
                valueCountSet = true;
            } else if (attributeValues.size() != valueCount) {
                final String msg = getLogPrefix() + " all attributes used in"
                        + " TemplateAttributeDefinition must have the same number of values.";
                log.error(msg);
                throw new ResolutionException(msg);
            }

            sourceValues.put(attributeName, attributeValues.iterator());
        }

        return valueCount;
    }

}