package org.apereo.cas.support.saml.services.idp.metadata.cache;

import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.support.saml.services.SamlRegisteredService;

import com.github.benmanes.caffeine.cache.Expiry;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import org.apache.commons.lang3.StringUtils;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.saml.criterion.EntityRoleCriterion;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;

import javax.annotation.Nonnull;
import java.util.concurrent.TimeUnit;

/**
 * This is {@link SamlRegisteredServiceMetadataExpirationPolicy}.
 *
 * @author Misagh Moayyed
 * @since 5.2.0
 */
@Slf4j
public class SamlRegisteredServiceMetadataExpirationPolicy implements Expiry<SamlRegisteredServiceCacheKey, MetadataResolver> {
    private final long defaultExpiration;

    public SamlRegisteredServiceMetadataExpirationPolicy(final long metadataCacheExpirationMinutes) {
        this.defaultExpiration = TimeUnit.MINUTES.toNanos(metadataCacheExpirationMinutes);
    }

    @Override
    public long expireAfterCreate(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,
                                  final MetadataResolver chainingMetadataResolver,
                                  final long currentTime) {
        val service = cacheKey.getRegisteredService();
        val duration = getCacheDurationForServiceProvider(service, chainingMetadataResolver);
        if (duration >= 0) {
            return duration;
        }
        LOGGER.debug("Metadata for [{}] does not define caching policies", service.getName());
        if (StringUtils.isNotBlank(service.getMetadataExpirationDuration())) {
            LOGGER.debug("Service [{}] defines a cache expiration duration of [{}]", service.getName(), service.getMetadataExpirationDuration());
            return Beans.newDuration(service.getMetadataExpirationDuration()).toNanos();
        }
        LOGGER.debug("Service [{}] does not define caching policies. Falling back onto default...", service.getName());
        return defaultExpiration;
    }

    private long getCacheDurationForServiceProvider(final SamlRegisteredService service, final MetadataResolver chainingMetadataResolver) {
        try {
            val set = new CriteriaSet();
            set.add(new EntityIdCriterion(service.getServiceId()));
            set.add(new EntityRoleCriterion(SPSSODescriptor.DEFAULT_ELEMENT_NAME));
            val entitySp = chainingMetadataResolver.resolveSingle(set);
            if (entitySp.getCacheDuration() != null) {
                LOGGER.debug("Located cache duration [{}] specified in SP metadata for [{}]", entitySp.getCacheDuration(), entitySp.getEntityID());
                return TimeUnit.MILLISECONDS.toNanos(entitySp.getCacheDuration());
            }

            set.clear();
            set.add(new EntityIdCriterion(service.getServiceId()));
            val entity = chainingMetadataResolver.resolveSingle(set);
            if (entity.getCacheDuration() != null) {
                LOGGER.debug("Located cache duration [{}] specified in entity metadata for [{}]", entity.getCacheDuration(), entity.getEntityID());
                return TimeUnit.MILLISECONDS.toNanos(entity.getCacheDuration());
            }
        } catch (final Exception e) {
            LOGGER.debug(e.getMessage(), e);
        }
        return -1;
    }

    @Override
    public long expireAfterUpdate(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,
                                  final MetadataResolver chainingMetadataResolver,
                                  final long currentTime, final long currentDuration) {
        LOGGER.debug("Cache expiration duration after updates is set to [{}]", currentDuration);
        return currentDuration;
    }

    @Override
    public long expireAfterRead(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,
                                final MetadataResolver chainingMetadataResolver,
                                final long currentTime, final long currentDuration) {
        LOGGER.debug("Cache expiration duration after reads is set to [{}]", currentDuration);
        return currentDuration;
    }
}

@@ -2234,27 +2234,1108 @@
 %0A    %7D%0A%0A
-    private
+%0A    @Override%0A    public long expireAfterUpdate(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,%0A                                  @Nonnull final MetadataResolver chainingMetadataResolver,%0A                                  final long currentTime, final long currentDuration) %7B%0A        LOGGER.debug(%22Cache expiration duration after updates is set to %5B%7B%7D%5D%22, currentDuration);%0A        return currentDuration;%0A    %7D%0A%0A    @Override%0A    public long expireAfterRead(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,%0A                                @Nonnull final MetadataResolver chainingMetadataResolver,%0A                                final long currentTime, final long currentDuration) %7B%0A        LOGGER.debug(%22Cache expiration duration after reads is set to %5B%7B%7D%5D%22, currentDuration);%0A        return currentDuration;%0A    %7D%0A%0A    /**%0A     * Gets cache duration for service provider.%0A     *%0A     * @param service                  the service%0A     * @param chainingMetadataResolver the chaining metadata resolver%0A     * @return the cache duration for service provider%0A     */%0A    protected
  long ge
@@ -3750,32 +3750,52 @@
     if (entitySp
+ != null && entitySp
 .getCacheDuratio
@@ -4236,24 +4236,42 @@
   if (entity
+ != null && entity
 .getCacheDur
@@ -4617,32 +4617,32 @@
 , e);%0A        %7D%0A
+
         return -
@@ -4653,819 +4653,7 @@
    %7D
-%0A%0A    @Override%0A    public long expireAfterUpdate(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,%0A                                  final MetadataResolver chainingMetadataResolver,%0A                                  final long currentTime, final long currentDuration) %7B%0A        LOGGER.debug(%22Cache expiration duration after updates is set to %5B%7B%7D%5D%22, currentDuration);%0A        return currentDuration;%0A    %7D%0A%0A    @Override%0A    public long expireAfterRead(@Nonnull final SamlRegisteredServiceCacheKey cacheKey,%0A                                final MetadataResolver chainingMetadataResolver,%0A                                final long currentTime, final long currentDuration) %7B%0A        LOGGER.debug(%22Cache expiration duration after reads is set to %5B%7B%7D%5D%22, currentDuration);%0A        return currentDuration;%0A    %7D
 %0A%7D%0A

clean up expiration policy NPEs