Let's say I have a controller which handles requests such as www.xyz.com/api/<someParam>. This is my controller, and my Service:
#Controller
public class MyController {
#Autowired MyService service;
#RequestMapping(value = "/api/{someParam}", method = RequestMethod.GET)
public String processRequest(
#PathVariable("someParam") String someParam) {
return service.processRequest(someParam);
}
}
#Service
public class MyService {
#Autowired APICaller apiCaller;
public String processRequest(someParam){
SomeObj obj = apiCaller.callApi();
// do something with obj
// return response;
}
}
Based on the param passed in the URL, I need to call some API, do some processing to the API response, and return it. All these APIs have different processing.
Let's say the APICaller interface is like this:
#Service
public interface APICaller {
public SomeObj callAPI();
}
#Service
public class ABC implements APICaller {
#Override
public SomeObj callAPI() {
// calls some REST api, does some processing to response and returns SomeObj
}
}
#Service
public class XYZ implements APICaller {
#Override
public SomeObj callAPI() {
// calls some SOAP api, does some processing to response and returns SomeObj
}
}
So if the param in the url is 'abc', I need to call ABCImpl. And if it is 'xyz', then I need to call XYZImpl. What should I do in the MyService class to instantiate the proper implementation? I might have multiple implementations based on the param, not just these two.
Thanks.
Define a named Map of beans in your configuration class.
#SpringBootApplication
public class Application {
#Bean
ABC abc() {
return new ABC();
}
#Bean
XYZ xyz() {
return new XYZ();
}
#Bean(name="apis")
Map<String, APICaller> apis() {
Map<String, APICaller> map = new HashMap<>();
map.put("xmz", xyz());
map.put("abc", abc());
return map;
}
}
Then inject it as follows:
#Service
public class MyService {
#Resource(name="apis")
Map<String, APICaller> apis;
public String processRequest(String param){
apis.get(param).callApi();
// do required null checks before calling callApi()
// do something with obj
// return response;
}
}
Update
As per you comment if you still wanted the dependencies of APICaller be autowired, this is how to to do it with #Bean
#Bean
ABC abc(DependancyBean1 bean2) {
return new ABC(bean1);
}
#Bean
XYZ xyz(DependancyBean2 bean2) {
return new XYZ(bean2);
}
Related
I have a #Controller that write a query result into the Model attributes.
When a GETquery arrives, it may be rewritten by a QuerydslBinderCustomizer to translate the query to the database fields accordingly:
#Controller
public class MyController {
#Autowired
private MyEntityRepository dao;
#GetMapping("/test")
public String findAll(Model model,
#QuerydslPredicate(root = MyEntity.class) Predicate predicate) {
model.addAttribute("page", dao.findAll(predicate, Pageable.of(5));
return "/test";
}
}
public interface MyEntityRepository extends
JpaRepository<MyEntity, Long>,
QuerydslPredicateExecutor<MyEntity>,
QuerydslBinderCustomizer<QMyEntity> {
#Override
default void customize(final QuerydslBindings bindings, final QMyEntity entity) {
bindings.bind(entity.somevalue).first((path, value) -> //... create a custom binding of it );
}
}
Question: how could I (unit)-test the QuerydslBinderCustomizer? Because: it is only rendered when a request arrives through the spring MVC layer.
In general, I could test the servlet as follows, but I'm then unable to read the return results from Model object:
#AutoConfigureMockMvc
public class MyTest {
#Test
public void test(#Autowired WebTestClient webTestClient) {
webTestClient.get()
.uri("/test?somevalue=junit")
.exchange()
.expectStatus().isOk();
//TODO how to access the 'Model.page' for assertion?
}
}
I want to pass SecurityExpressionRoot class, which i can access inside #PreAuthorise() annotation, to my custom checkAccess() method which checks access to specific method, using some logic based on authorities, roles and a additional variables that i pass to this method.
Inside #PreAuthorise() I can access methods from SecurityExpressionRoot, for example. hasAuthority()
Is there any way to do that?
Controller:
public class TestController {
private final PreAuthorizeChecker preAuthorizeChecker;
#Autowired
public TestController(PreAuthorizeChecker preAuthorizeChecker) {
this.preAuthorizeChecker = preAuthorizeChecker;
}
#GetMapping(path = "/test")
#PreAuthorize("#preAuthorizeChecker.checkAccess(/*SecurityExpressionRoot.getSomehow()*/)") //How to obtain SecurityExpressionRoot instance?
public ResponseEntity<Void> get() {
return;
}
PreAuthorizeChecker:
#Component
public class PreAuthorizeChecker {
#Autowired
public PreAuthorizeChecker() {
}
public boolean checkAccess(SecurityExpressionRoot securityExpressionRoot) {
//do sth with securityExpressionRoot
return true;
}
}
You might find that part 5 of this blog, A Custom Security Expression with Spring Security, on Baelding.com helpful. The author suggests extending SecurityExpressionRoot and adding your custom method to the new class like this:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public boolean checkAccess() {
//do sth with securityExpressionRoot
return true;
}
...
}
Then you will need to inject that new class into the expression handler like this:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
Finally, you just need to create the expression handler in your security config like this:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
return expressionHandler;
}
}
Then your new expression should be available:
#PreAuthorize("checkAccess()")
public ResponseEntity<Void> get() {
return;
}
I am working within an environment that changes credentials every several minutes. In order for beans that implement clients who depend on these credentials to work, the beans need to be refreshed. I decided that a good approach for that would be implementing a custom scope for it.
After looking around a bit on the documentation I found that the main method for a scope to be implemented is the get method:
public class CyberArkScope implements Scope {
private Map<String, Pair<LocalDateTime, Object>> scopedObjects = new ConcurrentHashMap<>();
private Map<String, Runnable> destructionCallbacks = new ConcurrentHashMap<>();
private Integer scopeRefresh;
public CyberArkScope(Integer scopeRefresh) {
this.scopeRefresh = scopeRefresh;
}
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name) || scopedObjects.get(name).getKey()
.isBefore(LocalDateTime.now().minusMinutes(scopeRefresh))) {
scopedObjects.put(name, Pair.of(LocalDateTime.now(), objectFactory.getObject()));
}
return scopedObjects.get(name).getValue();
}
#Override
public Object remove(String name) {
destructionCallbacks.remove(name);
return scopedObjects.remove(name);
}
#Override
public void registerDestructionCallback(String name, Runnable runnable) {
destructionCallbacks.put(name, runnable);
}
#Override
public Object resolveContextualObject(String name) {
return null;
}
#Override
public String getConversationId() {
return "CyberArk";
}
}
#Configuration
#Import(CyberArkScopeConfig.class)
public class TestConfig {
#Bean
#Scope(scopeName = "CyberArk")
public String dateString(){
return LocalDateTime.now().toString();
}
}
#RestController
public class HelloWorld {
#Autowired
private String dateString;
#RequestMapping("/")
public String index() {
return dateString;
}
}
When I debug this implemetation with a simple String scope autowired in a controller I see that the get method is only called once in the startup and never again. So this means that the bean is never again refreshed. Is there something wrong in this behaviour or is that how the get method is supposed to work?
It seems you need to also define the proxyMode which injects an AOP proxy instead of a static reference to a string. Note that the bean class cant be final. This solved it:
#Configuration
#Import(CyberArkScopeConfig.class)
public class TestConfig {
#Bean
#Scope(scopeName = "CyberArk", proxyMode=ScopedProxyMode.TARGET_CLASS)
public NonFinalString dateString(){
return new NonFinalString(LocalDateTime.now());
}
}
I have an interface and two implementations of that interface.
Now on the interface I am adding '#Component' annotation. One of the implementation has a '#primary' annotation which is only getting called up.
I want to call both the implementations when I call the interface's method from the autowired interface bean.
#Component
public interface CustomerPersister {
AbuserDetails setAbuserDetails(AbuserDetails customer);
}
#Primary
#Component
public class CustomerRedisPersisterImpl implements CustomerPersister{
#Autowired
private CustomerManager customerManager;
#Override
public AbuserDetails setAbuserDetails(AbuserDetails customer) {
if(customerManager.setAbuserDetails
(customer,ATSNamespaces.ABUSERDETAILS)){
return customer;
}else{
return new AbuserDetails();
}
}
#Component
public class MongoDbRepositoryImpl implements CustomerPersister{
#Autowired
MongoTemplate mongoTemplate;
#Override
public AbuserDetails setAbuserDetails(AbuserDetails customer) {
Query query = new Query(Criteria.where("login").is(customer.getLogin()));
System.out.println("query is:"+query);
Update update = new Update();
update.set("isReturnAbuser", customer.getIsReturnAbuser());
update.set("reasonReturnAbuser", customer.getReasonReturnAbuser());
update.set("isCODThrottled", customer.getIsCODThrottled());
update.set("reasonRTOAbuser", customer.getReasonRTOAbuser());
update.set("isFakeEmail", customer.getIsFakeEmail());
update.set("reasonFakeEmail", customer.getReasonFakeEmail());
update.set("amount",customer.getAmount());
WriteResult result = mongoTemplate.upsert(query, update, AbuserDetails.class);
System.out.println("This is the class that got returned:"+result.getClass());
System.out.println("New design result:"+result);
if(result!=null){
if(result.getN() != 0)
return customer;
else
return null;
}else
return null;
}
someOtherClass
#Autowired
private CustomerPersister customerPersister;
#Override
#RequestMapping(value = "/abuser/details/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public AbuserDetails setAbuserDetails(#RequestBody AbuserDetails customer){
return customerPersister.setAbuserDetails(customer);
}
You can tell Spring to autowire all implementations of an interface as a List and then call the method an all implementations.
class SomeClass {
List<CustomerPersister> customerPersisters;
#Autowired
SomeClass(List<CustomerPersister> customerPersisters) {
this.customerPersisters = customerPersisters;
}
public void setAbuserDetails(#RequestBody AbuserDetails customer) {
for (CustomerPersister customerPersister: customerPersisters) {
customerPersister.setAbuserDetails(customer);
}
}
}
Of course this will not allow you to return the result of customerPersister.setAbuserDetails(), because you can't return a single value from multiple persister calls. You either have to write some code in SomeClass that will determine which object should be returned or you could return a list of the results from all persisters. Or you have to redesign your interface to match the requirements.
I have a little problem. I think this is typical question. However, I can't find good example. My application is using Jersey. And I want to test controller by client as test. Controller has private field - StudentService. When I debug test I see, that field is null. This leads to error. And I need to inject this field. I tried this:
My Controller
#Path("/student")
#Component
public class StudentResourse {
#Autowired
private StrudentService service; // this field Spring does not set
#Path("/getStudent/{id}")
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Student getStudent(#PathParam("id") long id) {
return service.get(id);
}
}
My JUnit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:config.xml")
#TestExecutionListeners({ DbUnitTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class StudentResourseTest extends JerseyTest {
private static final String PACKAGE_NAME = "com.example.servlet";
private static final String FILE_DATASET = "/data.xml";
#Autowired
private StudentService service; // this field is setted by Spring, but I do not need this field for test
public StudentResourseTest() {
super(new WebAppDescriptor.Builder(PACKAGE_NAME).build());
}
#Override
protected TestContainerFactory getTestContainerFactory() {
return new HTTPContainerFactory();
}
#Override
protected AppDescriptor configure() {
return new WebAppDescriptor.Builder("restful.server.resource")
.contextParam("contextConfigLocation",
"classpath:/config.xml").contextPath("/")
.servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class).build();
}
#Test
#DatabaseSetup(FILE_DATASET)
public void test() throws UnsupportedEncodingException {
ClientResponse response = resource().path("student").path("getStudent")
.path("100500").accept(MediaType.APPLICATION_XML)
.get(ClientResponse.class);
Student student = (Student) response.getEntity(Student.class);
} }
I guees, that problem is in test class. Because, when I run my application not in test, I can directly request students and everything working fine. But when I test classes, internal field of Controller does not setted. How to fix this bug? Thanks for your answers.
This is in my config.xml
<context:component-scan base-package="com.example" />
<bean id="StudentResourse" class="com.example.servlet.StudentResourse">
<property name="service" ref="studentService" />
</bean>
<bean id="service" class="com.example.service.StudentServiceImpl" />
One issue may be that you're trying to configure your test application in constructor and in configure() method. Use one or another but not both because in this case your configure() method is not invoked and hence you may not be using SpringServlet and everything that is defined in this method.
Reference: https://github.com/jiunjiunma/spring-jersey-test and http://geek.riffpie.com/unit-testing-restful-jersey-services-glued-together-with-spring/
Idea is to get a hold of the application context inside jersey by using ApplicationContextAware interface. There after we can grab the exact bean already created by spring, in your case, StudentService. Below example shows a mocked version of the dependency, SampleService, used to test the resource layer apis.
Resource class delegating the processing to a service layer
#Component
#Path("/sample")
public class SampleResource {
#Autowired
private SampleService sampleService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path ("/{id}")
public Sample getSample(#PathParam("id") int id) {
Sample sample = sampleService.getSample(id);
if (sample == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return sample;
}
}
Service layer encapsulating business logic
#Service
public class SampleService {
private static final Map<Integer, Sample> samples = new HashMap<>();
static {
samples.put(1, new Sample(1, "sample1"));
samples.put(2, new Sample(2, "sample2"));
}
public Sample getSample(int id) {
return samples.get(id);
}
}
Unit test for the above resource
public class SampleResourceTest extends SpringContextAwareJerseyTest {
private SampleService mockSampleService;
// create mock object for our test
#Bean
static public SampleService sampleService() {
return Mockito.mock(SampleService.class);
}
/**
* Create our own resource here so only the test resource is loaded. If
* we use #ComponentScan, the whole package will be scanned and more
* resources may be loaded (which is usually NOT what we want in a test).
*/
#Bean
static public SampleResource sampleResource() {
return new SampleResource();
}
// get the mock objects from the internal servlet context, because
// the app context may get recreated for each test so we have to set
// it before each run
#Before
public void setupMocks() {
mockSampleService = getContext().getBean(SampleService.class);
}
#Test
public void testMock() {
Assert.assertNotNull(mockSampleService);
}
#Test
public void testGetSample() {
// see how the mock object hijack the sample service, now id 3 is valid
Sample sample3 = new Sample(3, "sample3");
Mockito.when(mockSampleService.getSample(3)).thenReturn(sample3);
expect().statusCode(200).get(SERVLET_PATH + "/sample/3");
String jsonStr = get(SERVLET_PATH + "/sample/3").asString();
Assert.assertNotNull(jsonStr);
}
}
SpringContextAwareJerseyTest
#Configuration
public class SpringContextAwareJerseyTest extends JerseyTest {
protected static String SERVLET_PATH = "/api";
final private static ThreadLocal<ApplicationContext> context =
new ThreadLocal<>();
protected String getResourceLocation() {
return "example.rest";
}
protected String getContextConfigLocation() {
return getClass().getName();
}
static private String getContextHolderConfigLocation() {
return SpringContextAwareJerseyTest.class.getName();
}
protected WebAppDescriptor configure() {
String contextConfigLocation = getContextConfigLocation() + " " +
getContextHolderConfigLocation();
Map<String, String> initParams = new HashMap<>();
initParams.put("com.sun.jersey.config.property.packages",
getResourceLocation());
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
return new WebAppDescriptor.Builder(initParams)
.servletClass(SpringServlet.class)
.contextParam(
"contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext")
.contextParam("contextConfigLocation", contextConfigLocation)
.servletPath(SERVLET_PATH) // if not specified, it set to root resource
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class)
.build();
}
protected final ApplicationContext getContext() {
return context.get();
}
#Bean
public static ContextHolder contextHolder() {
return new ContextHolder();
}
private static class ContextHolder implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context.set(applicationContext);
}
}
}
Using the above with jersey 1.8