/* * Copyright 2015 the original author or authors. * * 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.springframework.social.oauth2; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.junit.Test; import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; import org.springframework.util.ClassUtils; import org.springframework.web.client.RestTemplate; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; /** * Test methods for {@link AbstractOAuth2ApiBinding}, to verify the extension point where it's embedded * {@link RestTemplate} can be manipulated, such as wrapping it inside a AOP proxy. * * @author Greg Turnquist */ public class AbstractOAuth2ApiBindingTest { @Test public void testOriginalSocialTemplateToProveBackwardsCompatibility() throws Exception { MySocialTemplate template = new MySocialTemplate("some acces code"); template.afterPropertiesSet(); RestTemplate restTemplate = template.getRestTemplate(); assertThat(ClassUtils.isCglibProxy(restTemplate), is(false)); assertThat(template.getState(), equalTo("no state here")); assertThat(ClassUtils.isCglibProxy(template.getSubSocialTemplate().getRestTemplate()), is(false)); } @Test public void testAugmentedSocialTemplate() throws Exception { AugmentedSocialTemplate template = new AugmentedSocialTemplate("some access code", "key piece of data"); template.afterPropertiesSet(); RestTemplate restTemplate = template.getRestTemplate(); restTemplate.getMessageConverters(); assertThat(ClassUtils.isCglibProxy(restTemplate), is(true)); assertThat(template.getState(), equalTo("This template was touched by an aspect")); assertThat(ClassUtils.isCglibProxy(template.getSubSocialTemplate().getRestTemplate()), is(true)); } /** * Imaginary social service that extends the {@link AbstractOAuth2ApiBinding}. Used to * demonstrate default behavior of pass through on {@link RestTemplate}. * NOTE: This version is patterned like many Spring Social providers that "initSubApis()" * in the constructor call. Test up above show that backwards compatibility is NOT broken. */ private static class MySocialTemplate extends AbstractOAuth2ApiBinding { private String state = "no state here"; private MySubSocialTemplate subSocialTemplate; public MySocialTemplate(String accessToken) { super(accessToken); initSubApis(); } private void initSubApis() { this.subSocialTemplate = new MySubSocialTemplate(getRestTemplate()); } public String getState() { return state; } public void setState(String state) { this.state = state; } public MySubSocialTemplate getSubSocialTemplate() { return subSocialTemplate; } } /** * Imaginary social service that extends the {@link AbstractOAuth2ApiBinding}. Used to * demonstrate default behavior of pass through on {@link RestTemplate}. * NOTE: This version moves initSubApis() into the new configuration hook to make it more * extensible. */ private static class MyRefactoredSocialTemplate extends AbstractOAuth2ApiBinding { private String state = "no state here"; private MySubSocialTemplate subSocialTemplate; public MyRefactoredSocialTemplate(String accessToken) { super(accessToken); } private void initSubApis() { this.subSocialTemplate = new MySubSocialTemplate(getRestTemplate()); } public String getState() { return state; } public void setState(String state) { this.state = state; } public MySubSocialTemplate getSubSocialTemplate() { return subSocialTemplate; } @Override protected void postConstructionConfiguration() { initSubApis(); } } /** * Sample class that represent a subset of operations as commonly seen in Spring Social providers. */ private static class MySubSocialTemplate { private final RestTemplate restTemplate; public MySubSocialTemplate(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public RestTemplate getRestTemplate() { return restTemplate; } } /** * Extension of the imaginary soclail service. Uses Spring Social's extension point to wrap the embedded * {@link RestTemplate} with some advice. */ private static class AugmentedSocialTemplate extends MyRefactoredSocialTemplate { private final String keyPieceOfDataForAugmentation; public AugmentedSocialTemplate(String accessToken, String keyPieceOfDataForAugmentation) { super(accessToken); this.keyPieceOfDataForAugmentation = keyPieceOfDataForAugmentation; } @Override protected RestTemplate postProcess(RestTemplate restTemplate) { AspectJProxyFactory factory = new AspectJProxyFactory(restTemplate); factory.addAspect(new RestTemplateAdvice(this, this.keyPieceOfDataForAugmentation)); factory.setProxyTargetClass(true); return factory.getProxy(); } } /** * Some Spring AOP advice used to prove ability to wrap embedded {@link } */ @Aspect public static class RestTemplateAdvice { private final MyRefactoredSocialTemplate template; private final String keyPieceOfDataForAugmentation; public RestTemplateAdvice(MyRefactoredSocialTemplate template, String keyPieceOfDataForAugmentation) { this.template = template; this.keyPieceOfDataForAugmentation = keyPieceOfDataForAugmentation; } @Around("execution(* org.springframework.web.client.RestTemplate.*(..))") public Object around(ProceedingJoinPoint point) throws Throwable { template.setState("This template was touched by an aspect"); assertThat(this.keyPieceOfDataForAugmentation, is(notNullValue())); return point.proceed(); } } }