I am trying to impose time limit on http end points.
In the example below, I am aiming that this method shall be executed before 5 seconds. If it is taking more time, I would like to throw exception and return error to client.
Spring : 4.1.7
Jersey 1.1.9
Code
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
#Path("/pets")
#Component
public class PetsController {
#GET
#Produces({MediaTypeApi.JSON, MediaTypeApi.XML})
//Timeout of 5 secs
public List<Pet> getPets() {
//Return
}
}
Any idea to handle this in better way considering optimum utilization of threads.
EDIT
When writing this answer I didn't notice the version of Jersey OP is using. The async API was added in Jersey 2 therefore this answer is not an answer given OP's constraints.
EDIT 2
Apart from upgrading your Jersey libs you might consider migrating your api to Spring MVC and using their async API (available from Spring 3.2). Handling timeouts the Spring way (using the DeferredResult object):
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
#RestController
#RequestMapping("/api")
public class AsyncController {
private static final TIMEOUT = 5000L;
private final AService aService;
#Inject
public AsyncController(final AService aService) {
this.aService = aService;
}
#RequestMapping(value = "/async-endpoint", method = RequestMethod.GET)
public DeferredResult<ResponseEntity<ADto>> asyncEndpoint() {
DeferredResult<ResponseEntity<ADto>> deferredResult = new DeferredResult<>(TIMEOUT);
CompletableFuture
.supplyAsync(() -> aService.aVeryExpensiveOperation())
.thenAccept(result -> {
deferredResult.setResult(new ResponseEntity<>(result, HttpStatus.OK));
})
.exceptionally(throwable -> {
deferredResult.setErrorResult(
throwable instanceof CompletionException ? throwable.getCause() : throwable);
return null;
});
return deferredResult;
}
}
ORIGINAL ANSWER:
There is an example in Jersey Asynchronous Server API Documentation doing exactly what you want:
import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.TimeoutHandler;
import javax.ws.rs.core.Response;
import java.util.concurrent.TimeUnit;
#Path("/resource")
public class AsyncResource {
#GET
#Path("/timeoutAsync")
public void asyncGetWithTimeout(#Suspended final AsyncResponse asyncResponse) {
asyncResponse.setTimeoutHandler(new TimeoutHandler() {
#Override
public void handleTimeout(AsyncResponse asyncResponse) {
asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.").build());
}
});
asyncResponse.setTimeout(5, TimeUnit.SECONDS);
new Thread(new Runnable() {
#Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
return "Very Expensive Operation with Timeout";
}
}).start();
}
}
Please note that in a real life scenario you'd probably use a threadpool thread instead of creating it yourself like in this Jersey example
Related
I'm trying to persist the following entity when receiving a message from the client via Websocket:
import javax.persistence.Column;
import javax.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
#Entity
public class Penguin extends PanacheEntity{
#Column(name="penguin_name")
public String name;
}
The following persist works, when receiving a POST request:
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.transaction.Transactional;
import com.penguins.demo.pojos.Penguin;
#Path("/api")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class PenguinResource {
#GET
public List<Penguin> getPenguins(){
return Penguin.listAll();
}
#POST
#Transactional
public Response addPenguin(Penguin penguin){
penguin.persist();
return Response.ok(penguin).status(201).build();
}
}
However, the following code freezes when it reaches the persist line. The message.getPenguin() method is returning an actual Penguin reference (the MessageDecoder.class is doing it's part):
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.penguins.demo.pojos.Message;
import com.penguins.demo.pojos.Penguin;
#ServerEndpoint(value = "/waddle/{user}", decoders = MessageDecoder.class, encoders = MessageEncoder.class)
public class PenguinHub {
#OnMessage
#Transactional
public void onMessage(Session session, Message message) throws IOException {
// Handle new messages
message.setFrom(users.get(session.getId()));
// it freezes on persist :(
message.getPenguin().persist();
broadcast(message);
}
}
I'm new to Panache/Hibernate, any help would be apreciated, thank you.
It worked like this:
#Inject
ManagedExecutor managedExecutor;
#Inject
TransactionManager transactionManager;
#OnMessage
public void onMessage(Session session, Message message) throws IOException {
message.setFrom(users.get(session.getId()));
managedExecutor.submit(() -> {
try{
transactionManager.begin();
parseMessage(message); // persist the entity here
transactionManager.commit();
}catch(Exception e){
e.printStackTrace();
}
});
}
I am sending a get request to the server and server returns the following two responses. These responses are received as the event occurs on servers in streams (like id1,id2,id3,id4.....and so on) not in one shot.
Now, I need to take these response one by one and parse it and then save it the objects for further use.
How do i achieve this java 8 and spring MVC?
id: 1
data: {"event_type":"ABC","business_call_type":"XYZ","agent_number":"nnn","call_recording":null,"number":"0000","uuid":"a","call_direction":"Outbound","caller":"+100000000000","customer_number":"+100000000000","version":"1.0","k_number":"+917303454203","type":"AGENT_CALL","unique_id":"0","call_solution":"xx","FreeSWITCH_IPv4":"11111","Event_Date_Local":"2020-03-28 11:46:47"}
id: 2
data: {"event_type":"AGENT_ANSWER","business_call_type":"Outbound","agent_number":"+1111111111","call_recording":null,"number":"+22222222","uuid":"bbbbbbbbbbbbbb","call_direction":"Outbound","caller":"+100000000000","customer_number":"+100000000000","version":"1.0","k_number":"+1111111111","type":"AGENT_ANSWER","unique_id":"bbbbbbbbbb","call_solution":"xx","FreeSWITCH_IPv4":"0.0.0.0","Event_Date_Local":"2020-03-28 11:47:00"}
below is the code used foe above json parsing.
import java.util.HashMap;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.context.request.async.DeferredResult;
import com.psg.async_tasks.controller;
import com.psg.dao.CtiIntegrationdao;
// #Controller
#Service
public class ListningService {
private static final Logger logger = Logger.getLogger(ListningService.class.getName());
#Autowired
CtiIntegrationdao daoCtiInt;
//#RequestMapping({"list"})
#PostConstruct
public void ListningReponse() {
HashMap<String,String> results=daoCtiInt.getKnolarity_Config();
String endpoint;
endpoint=results.get("30");
endpoint=endpoint.replace("<<AUTH>>",results.get("26"));
logger.info(endpoint);
logger.info("============================================================================================#postconstruct=========");
AsyncRestTemplate asyncrestTemplate = new AsyncRestTemplate();
try {
final DeferredResult<String> result = new DeferredResult<>();
ListenableFuture<ResponseEntity<String>> futureEntity = asyncrestTemplate.getForEntity(endpoint, String.class);
logger.info("IN TRY");
logger.info(futureEntity.toString());
futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
#Override
public void onSuccess(ResponseEntity<String> result) {
String[] idno = result.getBody().split("\\R", 3);
System.out.println("==================="+idno[0]);
String responseBody =result.getBody().replaceAll("id: (\\d+)","").replace("data: ","");;
logger.info("-----responsebody-----"+responseBody);
logger.info("-----responsebody-----"+result.getBody());
// logger.info("-----responsebody-----"+result.getBody().getAgent_number());
// logger.info("-----responsebody-----"+result.getBody().getBusiness_call_type());
// logger.info("-----responsebody-----"+result.getBody().getCall_duration());
// logger.info("-----responsebody-----"+result.getBody().getCall_recording());
// logger.info("-----responsebody-----"+result.getBody().getCall_solution());
// logger.info("-----responsebody-----"+result.getBody().getCall_Type());
// logger.info("-----responsebody-----"+result.getBody().getDestination());
}
#Override
public void onFailure(Throwable ex) {
result.setErrorResult(ex.getMessage());
logger.info("------------Failure Block"+result.toString());
}
});
}catch(HttpClientErrorException ex) {
logger.info(ex.getMessage());
}catch(Exception ex) {
ex.printStackTrace();
}
}
}
I am new to groovy way of testing RestController for spring boot application. I have a Controller class :
#RestController
#RequestMapping(value = "/onboarding/v1")
public class OnboardingController {
private static final Logger LOG = LoggerFactory.getLogger(OnboardingServiceImpl.class);
#Autowired
private OnboardingService onboardingService;
#RequestMapping(
value = "/service-models",
method = RequestMethod.POST,
consumes = { "multipart/form-data" },
produces = { "application/json" }
)
public ResponseEntity createServiceModel(
#RequestParam("name") final String name,
#RequestParam("file") final MultipartFile file
){
try {
final ServiceModelRequestData serviceModelRequestData =
new ServiceModelRequestData(name, file);
final ServiceModelDetail createdServiceModel =
onboardingService.createServiceModel(serviceModelRequestData);
return new ResponseEntity<>(createdServiceModel, HttpStatus.OK);
}
catch (MalformedContentException ex) {
LOG.error("Malformed Content:", ex);
return new ResponseEntity<>(errorMessage(ex), HttpStatus.BAD_REQUEST);
}
catch (ServiceModelNameAlreadyExistsException ex) {
LOG.error("Service Model Name Already Exists:", ex);
return new ResponseEntity<>(errorMessage(ex), HttpStatus.CONFLICT);
}
catch (ServiceUnavailableException ex) {
LOG.error("Service Unavailable currently:" + ex);
return new ResponseEntity<>(errorMessage(ex), HttpStatus.SERVICE_UNAVAILABLE);
}
}
//...
}
I am unable to find how to spock test the above class along with writing the spock test cases for exceptions and checking the desired response?I want to create a test case which throws an error when the method is called and returns the responseentity which I want to check contains the given Jason data along with the desired Http Status. Sample spock test for the code snippet would be highly appreciated.
EDITED:
The test Class is as below(along with comments of what I was trying to do):
package com.service.onboarding.web.controller
import spock.lang.Specification
import com.service.onboarding.business.OnboardingServiceImpl
import com.service.onboarding.business.api.OnboardingService
import com.service.onboarding.domain.exception.MalformedContentException
import com.service.onboarding.domain.exception.ServiceModelNameAlreadyExistsException
import com.service.onboarding.domain.exception.ServiceUnavailableException
import com.service.onboarding.domain.resource.Greeting
import groovy.json.internal.Exceptions
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.web.multipart.MultipartFile
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.ResponseEntity
import spock.lang.Unroll
import static org.mockito.BDDMockito.given
/*#WebMvcTest
public class OnboardingControllerSpec extends Specification {
#MockBean
private OnboardingService onboardingService;
#Autowired
private MockMvc mockMvc;
def "controller should return expected JSON content and OK response"() {
given: 'hello world service responds with greeting'
def name = "Emily"
given(onboardingService.getPersonalGreeting("${name}")).willReturn(new Greeting(1, "Hi, ${name}"));
when: 'hello world service is called with name provided'
def response = mockMvc.perform(get("/onboarding/v1?name=${name}"))
then: 'expected JSON returned and response code is OK'
response
.andExpect(status().isOk())
.andExpect(content().json("{'id': 1, 'content': 'Hi, ${name}'}"))
}
}*/
public class OnboardingControllerSpec extends Specification{
OnboardingServiceImpl service =new OnboardingServiceImpl()
OnboardingController controller
ResponseEntity response
#Unroll
def "HTTP response #statusCode when creating service model"() {
given:
if (exception) {
service = Stub() {
createServiceModel(_) >> { throw exception }
}
}
controller= new OnboardingController(onboardingService: service)
when:
response=controller.createServiceModel("test", Mock(MultipartFile))
then:
response.statusCode == statusCode
where:
exception | statusCode
null | OK
new MalformedContentException() | BAD_REQUEST
new ServiceModelNameAlreadyExistsException() | CONFLICT
new ServiceUnavailableException() | SERVICE_UNAVAILABLE
}
}
My service class is as follows:
package com.service.onboarding.business;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.service.onboarding.business.api.OnboardingService;
import com.service.onboarding.business.servicemanagement.ServiceModelRepository;
import com.service.onboarding.domain.exception.MalformedContentException;
import com.service.onboarding.domain.exception.ServiceModelDoesNotExistException;
import com.service.onboarding.domain.exception.ServiceModelInUseException;
import com.service.onboarding.domain.exception.ServiceModelNameAlreadyExistsException;
import com.onboarding.domain.exception.ServiceUnavailableException;
import com.service.onboarding.domain.requestdata.ServiceModelPaginationFilter;
import com.service.onboarding.domain.requestdata.ServiceModelRequestData;
import com.service.onboarding.domain.resource.Greeting;
import com.service.onboarding.domain.resource.ServiceModel;
import com.service.onboarding.domain.resource.ServiceModelDetail;
import com.service.onboarding.domain.responsedata.PaginatedServiceResponseData;
/*
* Sample service to demonstrate what the API would use to get things done
*/
#Service
public class OnboardingServiceImpl implements OnboardingService {
private final AtomicLong counter = new AtomicLong();
private static final String TEMPLATE = "Hello, %s!";
private static final Logger LOG = LoggerFactory.getLogger(OnboardingServiceImpl.class);
#Autowired
private ServiceModelRepository serviceModelRepository;
public OnboardingServiceImpl() {
}
#Override
public Greeting getPersonalGreeting(final String name) {
return new Greeting(counter.incrementAndGet(),
String.format(TEMPLATE, name));
}
#Override
public ServiceModelDetail createServiceModel(final ServiceModelRequestData serviceModelRequestData) throws MalformedContentException, ServiceModelNameAlreadyExistsException, ServiceUnavailableException {
return serviceModelRepository.create(serviceModelRequestData);
}
Your test should look like this:
package de.scrum_master.stackoverflow
import org.springframework.http.ResponseEntity
import org.springframework.web.multipart.MultipartFile
import spock.lang.Specification
import spock.lang.Unroll
import static org.springframework.http.HttpStatus.*
class OnboardingControllerTest extends Specification {
OnboardingService service = new OnboardingService()
OnboardingController controller
ResponseEntity response
#Unroll
def "HTTP response #statusCode when creating service model"() {
given:
if (exception) {
service = Stub() {
createServiceModel(_) >> { throw exception }
}
}
controller = new OnboardingController(onboardingService: service)
when:
response = controller.createServiceModel("test", Mock(MultipartFile))
then:
response.statusCode == statusCode
where:
exception | statusCode
null | OK
new MalformedContentException() | BAD_REQUEST
new ServiceModelNameAlreadyExistsException() | CONFLICT
new ServiceUnavailableException() | SERVICE_UNAVAILABLE
}
}
The console log (I switched to Java logging when replicating your use case) would be:
Mär 10, 2018 12:51:50 PM de.scrum_master.stackoverflow.OnboardingController createServiceModel
INFORMATION: Malformed Content: de.scrum_master.stackoverflow.MalformedContentException
Mär 10, 2018 12:51:50 PM de.scrum_master.stackoverflow.OnboardingController createServiceModel
INFORMATION: Service Model Name Already Exists: de.scrum_master.stackoverflow.ServiceModelNameAlreadyExistsException
Mär 10, 2018 12:51:50 PM de.scrum_master.stackoverflow.OnboardingController createServiceModel
INFORMATION: Service Unavailable currently: de.scrum_master.stackoverflow.ServiceUnavailableException
In my IDE (IntelliJ IDEA) the test result looks like this:
P.S.:
I know you are new to SO. So this was your free shot. Next time please provide an MCVE, i.e. a minimal, compilable and executable example, not just a code snippet without package name, imports and test class. You want help with Spock, so please write a Spock specification (test class), show it to us and explain your problem with it. That way we do not need to recreate your situation from scratch but can just concentrate on fixing your test and solving your problem.
You were lucky I just felt a bit bored, waiting for a guest to arrive, otherwise I would not have done that. Saying "I have nothing, please do everything for me" only shows that you are lazy. If you want help, make it easy for others to help and show some respect towards their time budget. They are doing it for free!
I am using Spring to achieve the following:
On a server, I receive data via a REST interface in an XML-Format. I want to transform the data into JSON and POST it to another Server. My code (I removed some sensitive classnames/URLs to avoid the wrath of my employer) looks like this:
Main/Configuration class:
package stateservice;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
#SpringBootApplication
public class App {
Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
System.out.println("Start!");
SpringApplication.run(StateServiceApplication.class, args);
System.out.println("End!");
}
#Bean
public RestTemplate restTemplate() {
log.trace("restTemplate()");
HttpHost proxy = new HttpHost("proxy_url", 8080);
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(50);
RequestConfig requestConfig = RequestConfig.custom().setProxy(proxy).build();
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setDefaultRequestConfig(requestConfig);
httpClientBuilder.setConnectionManager(cm);
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClientBuilder.build());
return new RestTemplate(requestFactory);
}
}
The class representing the RESTful interface:
package stateservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import foo.bar.XmlData
#RestController
public class StateController {
private static Logger log = LoggerFactory.getLogger(DataController.class);
#Autowired
ForwarderService forwarder;
#RequestMapping(value = "/data", method = RequestMethod.POST)
public String postState(#RequestBody XmlData data) {
forwarder.forward(data);
return "Done!";
}
}
Finally, the Forwarder:
package stateservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import foo.bar.Converter;
import foo.bar.XmlData;
#Service
public class ForwarderService {
private static Logger log = LoggerFactory.getLogger(ForwarderService.class);
String uri = "forward_uri";
#Autowired
RestTemplate restTemplate;
#Async
public String forward(XmlData data) {
log.trace("forward(...) - start");
String json = Converter.convert(data);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ResponseEntity<String> response = restTemplate.postForEntity(uri,
new HttpEntity<String>(json, headers), String.class);
// responseEntity.getBody();
// log.trace(responseEntity.toString());
log.trace("forward(...) - end");
return response.getBody();
}
}
However, the Connection Manager seldomly seems to release connections for reuse, and additionally, the system gets flooded with connections in the CLOSE_WAIT state (which can be seen using netstat). All connections in the pool get leased, but not released, and as soon as the number of connections in the CLOSE_WAIT state reaches the ulimit, I get 'Too many open files'-exceptions
Because of the multithreaded nature of the code, I suspect that sockets cannot be closed/connections be released, because some other thread is somhow blocking them.
I would really appreciate any help or any hint you can give me to solve the problem.
There is a trick with Apache HttpEntity - to release locked connection - response has to be FULLY consumed and closed. See EntityUtils and HttpEntity docs for details:
EntityUtils.consume(response);
Since version 4.3 Apache HttpClient releases connection back to the pool when #close() method is called on the CloseableHttpResponse.
However this feature is supported by Spring Web only since version 4.0.0-RELEASE, see method #close() in HttpComponentsClientHttpResponse.java:
#Override
public void close() {
// Release underlying connection back to the connection manager
try {
try {
// Attempt to keep connection alive by consuming its remaining content
EntityUtils.consume(this.httpResponse.getEntity());
} finally {
// Paranoia
this.httpResponse.close();
}
}
catch (IOException ignore) {
}
}
The key to success is the line marked by "// Paranoia" - explicit .close() call. It actually releases connection back to pool.
I am using Guice + Jersey + Shiro to login via a REST API and then use the same HTTP session under which I logged in to and have my permissions work for that resource.
Below is my code.
Firstly, my servlet configuration:-
public class ServletConfiguration extends GuiceServletContextListener
{
private ServletContext mServletContext;
#Override
public void contextInitialized(ServletContextEvent inEvent)
{
mServletContext = inEvent.getServletContext();
super.contextInitialized(inEvent);
}
#Override
protected Injector getInjector()
{
mServletContext.addListener(new au.com.tt.agora.configuration.CbiCleanupHttpSessionListener());
return Guice.createInjector(new JerseyServletModule() {
#Override
protected void configureServlets()
{
install(new TTShiroWebModule(mServletContext));
install(new ShiroAopModule());
filter("/*").through(GuiceShiroFilter.class);
bind(ShiroLoginResource.class);
bind(ShiroResource.class);
filter("/*").through(GuiceContainer.class);
}
});
}
}
Now, this is my test realm:-
package au.com.tt.agora.configuration.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class TestRealm extends AuthorizingRealm
{
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken inToken) throws AuthenticationException
{
UsernamePasswordToken upToken = (UsernamePasswordToken) inToken;
if (upToken.getUsername().equals("Kamal") || upToken.getUsername().equals("NotKamal"))
return new SimpleAuthenticationInfo(upToken.getUsername(), upToken.getPassword(), getName());
return null;
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection inPrincipals)
{
String username = (String) inPrincipals.fromRealm(getName()).iterator().next();
SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
if (username.equals("Kamal"))
{
authzInfo.addStringPermission("PRODMA:READ:AU");
authzInfo.addStringPermission("PRODMA:WRITE:KB");
authzInfo.addStringPermission("SUPPMA:READ:KB");
}
else
{
authzInfo.addStringPermission("PRODMA:READ:AU");
authzInfo.addStringPermission("PRODMA:WRITE:KB");
}
return authzInfo;
}
}
This is the web module
package au.com.tt.agora.configuration.shiro;
import javax.servlet.ServletContext;
import org.apache.shiro.guice.web.ShiroWebModule;
public class TTShiroWebModule extends ShiroWebModule
{
public TTShiroWebModule(ServletContext inServletContext)
{
super(inServletContext);
}
#SuppressWarnings("unchecked")
#Override
protected void configureShiroWeb()
{
bindRealm().to(TestRealm.class);
addFilterChain("**/shiroResource/*", ANON);
}
}
Here is the resource I use to login:-
package au.com.tt.agora.configuration.jaxrs.resources;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import com.google.inject.Inject;
import au.com.tt.agora.configuration.option.ClientProvider;
import au.com.tt.agora.configuration.option.ConfigurationProvider;
import au.com.tt.agora.login.web.request.LoginRequest;
import au.com.tt.agora.login.web.request.LoginResponse;
import au.com.tt.agora.login.web.service.LoginHandler;
import au.com.tt.calypso.cbi.CalypsoException;
#Path("/{client}/shiroLogin")
public class ShiroLoginResource
{
private static final String ROUTING_TOKEN_HEADER = "proxy-jroute";
#POST
#Path("/standard")
#Produces(MediaType.TEXT_PLAIN)
#Consumes(MediaType.APPLICATION_JSON)
public String login(#Context HttpServletRequest inServletRequest) throws CalypsoException
{
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken("Kamal", "Password", false));
return getSessionIdWithRouting(inServletRequest);
}
private String getSessionIdWithRouting(HttpServletRequest inRequest)
{
String sessionId = inRequest.getSession().getId();
return(sessionId);
}
}
And here is the resource I am calling:-
package au.com.tt.agora.configuration.jaxrs.resources;
import javax.servlet.http.HttpSession;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
#Path("/{client}/shiroResource")
public class ShiroResource
{
private static final Logger LOG = LoggerFactory.getLogger(ShiroResource.class);
#Inject
public ShiroResource()
{
}
#POST
#Path("requiresProdma.do")
#Produces(MediaType.TEXT_PLAIN)
#RequiresPermissions({ "PRODMA:*:*" })
public String prodmaRequired()
{
return "Success";
}
#POST
#Path("requiresSuppma.do")
#Produces(MediaType.TEXT_PLAIN)
#RequiresPermissions({ "SUPPMA:*:*" })
public String suppmaRequired()
{
Subject subject = SecurityUtils.getSubject();
subject.getPrincipal();
return "Success";
}
}
If I put a breakpoint into suppmaRequired and call this resource, I can see that subject is not authenticated.
My understanding on how Shiro works is obviously faulty, but I don't know what I am not doing. Can anyone point me in the right direction?
Not sure if it makes a difference, but I am using URL rewriting to access the web session.
Basically, I am using the fetch API to test this. Here is an example:-
fetch("http://localhost/app/tt/shiroLogin/standard", {
method: "POST",
headers: {
"Content-Type" : "application/json"
} ,
body: '{"username":"myName","password":"myPassword"}'
})
.then(function(res) {
return res.text();
})
.then(function(sessionId) {
return fetch("http://localhost/app/tt/shiroResource/requiresSuppma.do;JSESSIONID=" + sessionId,
{
method: "POST"
});
})
.then(function(res) {
return res.text();
})
.then(function(res) {
console.log(res);
});
I am also deploying to glassfish.
OK, this was not a Shiro problem in the end. I was using two different sessions going from the ShiroLoginResource to ShiroResource.
I forgot that you actually needed to inject with a session level object in Guice to force Guice to create a session. Stupid me.
Once I injected a session scoped dependency into ShiroLoginResource and interacted with it, then everything just worked.
I will keep this question open because it gives some useful code snippets.