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.servlet; 018 019import java.util.function.Supplier; 020 021import javax.servlet.http.HttpServletRequest; 022 023import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 024import org.springframework.context.ApplicationContext; 025import org.springframework.security.web.util.matcher.RequestMatcher; 026import org.springframework.util.Assert; 027import org.springframework.web.context.WebApplicationContext; 028import org.springframework.web.context.support.WebApplicationContextUtils; 029 030/** 031 * {@link ApplicationContext} backed {@link RequestMatcher}. Can work directly with the 032 * {@link ApplicationContext}, obtain an existing bean or 033 * {@link AutowireCapableBeanFactory#createBean(Class, int, boolean) create a new bean} 034 * that is autowired in the usual way. 035 * 036 * @param <C> the type of the context that the match method actually needs to use. Can be 037 * an {@link ApplicationContext} or a class of an {@link ApplicationContext#getBean(Class) 038 * existing bean}. 039 * @author Phillip Webb 040 * @since 2.0.0 041 */ 042public abstract class ApplicationContextRequestMatcher<C> implements RequestMatcher { 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 ApplicationContextRequestMatcher(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 boolean matches(HttpServletRequest request) { 057 return matches(request, getContext(request)); 058 } 059 060 /** 061 * Decides whether the rule implemented by the strategy matches the supplied request. 062 * @param request the source request 063 * @param context a supplier for the initialized context (may throw an exception) 064 * @return if the request matches 065 */ 066 protected abstract boolean matches(HttpServletRequest request, Supplier<C> context); 067 068 private Supplier<C> getContext(HttpServletRequest request) { 069 if (this.context == null) { 070 synchronized (this.contextLock) { 071 if (this.context == null) { 072 Supplier<C> createdContext = createContext(request); 073 initialized(createdContext); 074 this.context = createdContext; 075 } 076 } 077 } 078 return this.context; 079 } 080 081 /** 082 * Called once the context has been initialized. 083 * @param context a supplier for the initialized context (may throw an exception) 084 */ 085 protected void initialized(Supplier<C> context) { 086 } 087 088 @SuppressWarnings("unchecked") 089 private Supplier<C> createContext(HttpServletRequest request) { 090 WebApplicationContext context = WebApplicationContextUtils 091 .getRequiredWebApplicationContext(request.getServletContext()); 092 if (this.contextClass.isInstance(context)) { 093 return () -> (C) context; 094 } 095 return () -> context.getBean(this.contextClass); 096 } 097 098}