001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.security.reactive; 018 019import java.util.function.Supplier; 020 021import reactor.core.publisher.Mono; 022 023import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 024import org.springframework.context.ApplicationContext; 025import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; 026import org.springframework.util.Assert; 027import org.springframework.web.server.ServerWebExchange; 028 029/** 030 * {@link ApplicationContext} backed {@link ServerWebExchangeMatcher}. Can work directly 031 * with the {@link ApplicationContext}, obtain an existing bean or 032 * {@link AutowireCapableBeanFactory#createBean(Class, int, boolean) create a new bean} 033 * that is autowired in the usual way. 034 * 035 * @param <C> the type of the context that the match method actually needs to use. Can be 036 * an {@link ApplicationContext} or a class of an {@link ApplicationContext#getBean(Class) 037 * existing bean}. 038 * @author Madhura Bhave 039 * @since 2.0.0 040 */ 041public abstract class ApplicationContextServerWebExchangeMatcher<C> 042 implements ServerWebExchangeMatcher { 043 044 private final Class<? extends C> contextClass; 045 046 private volatile Supplier<C> context; 047 048 private final Object contextLock = new Object(); 049 050 public ApplicationContextServerWebExchangeMatcher(Class<? extends C> contextClass) { 051 Assert.notNull(contextClass, "Context class must not be null"); 052 this.contextClass = contextClass; 053 } 054 055 @Override 056 public final Mono<MatchResult> matches(ServerWebExchange exchange) { 057 return matches(exchange, getContext(exchange)); 058 } 059 060 /** 061 * Decides whether the rule implemented by the strategy matches the supplied exchange. 062 * @param exchange the source exchange 063 * @param context a supplier for the initialized context (may throw an exception) 064 * @return if the exchange matches 065 */ 066 protected abstract Mono<MatchResult> matches(ServerWebExchange exchange, 067 Supplier<C> context); 068 069 protected Supplier<C> getContext(ServerWebExchange exchange) { 070 if (this.context == null) { 071 synchronized (this.contextLock) { 072 if (this.context == null) { 073 Supplier<C> createdContext = createContext(exchange); 074 initialized(createdContext); 075 this.context = createdContext; 076 } 077 } 078 } 079 return this.context; 080 } 081 082 /** 083 * Called once the context has been initialized. 084 * @param context a supplier for the initialized context (may throw an exception) 085 */ 086 protected void initialized(Supplier<C> context) { 087 } 088 089 @SuppressWarnings("unchecked") 090 private Supplier<C> createContext(ServerWebExchange exchange) { 091 ApplicationContext context = exchange.getApplicationContext(); 092 Assert.state(context != null, 093 "No ApplicationContext found on ServerWebExchange."); 094 if (this.contextClass.isInstance(context)) { 095 return () -> (C) context; 096 } 097 return () -> context.getBean(this.contextClass); 098 } 099 100}