I'm trying to call camel/java-ee/class (it's very long programs, so no time to convert) from a spring-boot. So for now the solution is to just start with spring-boot and call the main class.
Here's the main application of springboot that will call the javaee/class, I tried the simple bean calling before, so this my latest test for I thought its about the proper call of bean, but unfortunately, even the proper calling of bean have the same error as the simple calling of bean,
#SpringBootApplication
public class App {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = SpringApplication.run(App.class, args);
BeanAppService service = applicationContext.getBean(BeanAppService.class);
service.callInitCamelJavaEE();
}
}
Service interface,
public interface BeanAppService {
void callInitCamelJavaEE() throws Exception;
}
Configuration,
#Configuration
public class BeanAppConfig {
#Bean
public BeanAppService beanAppConfig() {
return new BeanApp();
}
}
BeanApp (this is the original main class with args in java/ee),
public class BeanApp implements BeanAppService {
public BeanApp() {}
protected <I extends SpongeRequest, O extends SpongeResponse> void createOperation(RestDefinition restDefinition, String operation,
String operationDescription, Class<I> requestClass, String requestDescription, Class<O> responseClass,
String responseDescription, BiFunction<I, Exchange, O> operationHandler) {
restDefinition.post(operation).description(operationDescription).type(requestClass).outType(responseClass).param().name("body")
.type(body).description(requestDescription).endParam().responseMessage().code(200).message(responseDescription)
.endResponseMessage().route().id("sponge-" + operation).process(exchange -> {
String requestBody = exchange.getIn().getBody(String.class);
if (logger.isDebugEnabled()) {
logger.debug("REST API {} request: {}", operation, RestApiUtils.obfuscatePassword(requestBody));
}
try {
setupResponse(operation, exchange,
operationHandler.apply(getObjectMapper().readValue(requestBody, requestClass), exchange));
} catch (Throwable processingException) {
logger.info("REST API error", processingException);
try {
setupResponse(operation, exchange, apiService.createGenericErrorResponse(processingException, exchange));
} catch (Throwable e) {
logger.error("REST API send error response failure", e);
throw e;
}
}
}).endRest();
}
#Override
public void callInitCamelJavaEE() throws Exception {
try (CamelContext camelContext = new DefaultCamelContext()) {
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
restConfiguration().enableCORS(true).setPort("8080");
}
});
camelContext.start();
Thread.sleep(6000000);
camelContext.stop();
}
}
The error,
StreamCaching is not in use. If using streams then it's recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
Error starting CamelContext (camel-2) due to exception thrown: Failed to start route route1 because of null
org.apache.camel.FailedToStartRouteException: Failed to start route route1 because of null
at org.apache.camel.impl.engine.RouteService.warmUp(RouteService.java:122) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.InternalRouteStartupManager.doWarmUpRoutes(InternalRouteStartupManager.java:270) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.InternalRouteStartupManager.safelyStartRouteServices(InternalRouteStartupManager.java:157) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.InternalRouteStartupManager.doStartOrResumeRoutes(InternalRouteStartupManager.java:115) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.doStartCamel(AbstractCamelContext.java:2889) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.doStartContext(AbstractCamelContext.java:2702) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.doStart(AbstractCamelContext.java:2665) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.support.service.BaseService.start(BaseService.java:115) ~[camel-api-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.start(AbstractCamelContext.java:2431) ~[camel-base-engine-3.7.0.jar:3.7.0]
at ca.toronto.eis.dwork2.BeanApp.BeanAppApp.callMain(BeanAppApp.java:531) ~[main/:?]
at ca.toronto.eis.dwork2.Dwork2Application.main(Dwork2Application.java:29) ~[main/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_202]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_202]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_202]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_202]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.0.jar:2.7.0]
Caused by: java.lang.IllegalStateException: Cannot find RestConsumerFactory in Registry or as a Component to use
at org.apache.camel.component.rest.RestEndpoint.createConsumer(RestEndpoint.java:602) ~[camel-rest-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.DefaultRoute.addServices(DefaultRoute.java:575) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.DefaultRoute.onStartingServices(DefaultRoute.java:160) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.RouteService.doWarmUp(RouteService.java:150) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.RouteService.warmUp(RouteService.java:120) ~[camel-base-engine-3.7.0.jar:3.7.0]
... 15 more
Apache Camel 3.7.0 (camel-2) is shutting down
Apache Camel 3.7.0 (camel-2) uptime 28ms
Apache Camel 3.7.0 (camel-2) is shutdown in 10ms
Anyone who have encountered this before?
Seems like in the route builder you have not configured any route.
#Override
public void callInitCamelJavaEE() throws Exception {
try (CamelContext camelContext = new DefaultCamelContext()) {
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
restConfiguration().enableCORS(true).setPort("8080");
}
});
camelContext.start();
Thread.sleep(6000000);
camelContext.stop();
}
}
Assuming you are using Rest here is How to configure a route in RouteBuilder using REST DSL
Related
I want to handle any exception from feign client, even if service is not available. However I can not catch them using try/catch. This is my feign client:
#FeignClient(name = "api-service", url ="localhost:8888")
public interface ClientApi extends SomeApi {
}
Where api is:
#Path("/")
public interface SomeApi {
#GET
#Path("test")
String getValueFromApi();
}
Usage of client with try/catch:
#Slf4j
#Service
#AllArgsConstructor
public class SampleController implements SomeApi {
#Autowired
private final ClientApi clientApi;
#Override
public String getValueFromApi() {
try {
return clientApi.getValueFromApi();
} catch (Throwable e) {
log.error("CAN'T CATCH");
return "";
}
}
}
Dependencies are in versions:
spring-boot 2.2.2.RELEASE
spring-cloud Hoxton.SR1
Code should work according to How to manage Feign errors?.
I received few long stack traces among them exceptions are :
Caused by: java.net.ConnectException: Connection refused (Connection refused)
Caused by: feign.RetryableException: Connection refused (Connection refused) executing GET http://localhost:8888/test
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: ClientApi#getValueFromApi() failed and no fallback available.
How to properly catch Feign exeptions, even if client service (in this case localhost:8888) is not available?
Ps. When feign client service is available it works, ok. I am just focused on the exceptions aspect.
A better way to handle the situation where your service is not available is to use a circuit breaker pattern. Fortunately, it is easy using Netflix Hystrix as an implementation of the circuit breaker pattern.
First of all, you need to enable Hystrix for feign clients in application configuration.
application.yml
feign:
hystrix:
enabled: true
Then you should write a fallback class for the specified feign client interface.
In this case getValueFormApi method in fallback class will act mostly like catch block that you wrote(with exception when circuit will be in open state and original method will not be attempted).
#Component
public class ClientApiFallback implements ClientApi {
#Override
public String getValueFromApi(){
return "Catch from fallback";
}
}
Lastly, you just need to specify the fallback class for your feign client.
#FeignClient(name = "api-service", url ="localhost:8888", fallback = ClientApiFallback.class)
public interface ClientApi extends SomeApi {
}
That way your method getValueFromApi is fail safe. If,
for any reason, any uncaught exceptions escape from getValueFromApi the ClientApiFallback method will be called.
To enable circuit breaker and also configure your application to deal with unexpected errors, you need to:
1.- Enable the circuit breaker itself
#SpringBootApplication
#EnableFeignClients("com.perritotutorials.feign.client")
#EnableCircuitBreaker
public class FeignDemoClientApplication {
2.- Create your fallback bean
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
#Setter
private Throwable cause;
#Override
public void savePet(#RequestBody Map<String, ?> pet) {
log.error("You are on fallback interface!!! - ERROR: {}", cause);
}
}
Some things you must keep in mind for fallback implementations:
Must be marked as #Component, they are unique across the application.
Fallback bean should have a Prototype scope because we want a new one to be created for each exception.
Use constructor injection for testing purposes.
3.- Your ErrorDecoder, to implement fallback startegies depending on the HTTP error returned:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyCustomBadRequestException();
}
if (response.status() >= 500) {
return new RetryableException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
4.- In your configuration class, add the Retryer and the ErrorDecoder into the Spring context:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default();
}
You can also add customization to the Retryer:
class CustomRetryer implements Retryer {
private final int maxAttempts;
private final long backoff;
int attempt;
public CustomRetryer() {
this(2000, 5); //5 times, each 2 seconds
}
public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
#Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}
}
If you want to get a functional example about how to implement Feign in your application, read this article.
I am calling another microservice once my current microservice is up and ready using feign client in my current microservice built using Jhipster.
So my Feign Interface is
package com.persistent.integration.client;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.persistent.integration.service.dto.DataPipelineDTO;
#AuthorizedFeignClient(name = "Integrationconfiguration")
public interface DataPipelinesResourceFeign {
#RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)
List<DataPipelineDTO> getAllDataPipelines(#RequestParam(value = "pageable") Pageable pageable );
}
}
And I have implemented ApplicationRunner where I have called feign client method.
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Autowired
private DataPipelinesResourceFeign dataPipelinesResourceFeign;
#Autowired
private ActiveMQListener activeMqListener;
#Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(0, 20);
try {
List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable); //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());
allStartedDataPipeLines.forEach(datapipe ->
{
try {
activeMqListener.consume(datapipe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
But after running this, it gives below exception at dataPipelinesResourceFeign.getAllDataPipelines :
com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'request' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton; nested exception is java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:362)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB>eanFactory.java:199)
at
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge>tSource.java:35)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.>java:193)
at com.sun.proxy.$Proxy147.getAccessToken(Unknown Source) at
com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza>tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)
at
com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay>RequestInterceptor.java:23)
at
feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at
feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at
rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 68 more Caused by: java.lang.IllegalStateException: No
thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.web.context.request.RequestContextHolder.currentRequestAttr>ibutes(RequestContextHolder.java:131)
at
org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst>ractRequestAttributesScope.java:42)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:350)
many suggestions on internet were to add listerner RequestContextListener. But problem persisted even if I added listener in webConfigurer.java in onStartup method.
{
servletContext.addListener(RequestContextListener.class);
}
But of no use.
Any leads would be appreciated.
I found a workaround for this. I don't know why TokenRelayRequestIntercepton isn't working but you can use your own RequestInterceptor based on Spring's SecurityContext.
First, define a RequestInterceptor :
public class MyRequestInterceptor implements RequestInterceptor {
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";
public MyRequestInterceptor() {
super();
}
#Override
public void apply(RequestTemplate template) {
// demander un token à keycloak et le joindre à la request
Optional<String> header = getAuthorizationHeader();
if (header.isPresent()) {
template.header(AUTHORIZATION, header.get());
}
}
public static Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
(OAuth2AuthenticationDetails) authentication.getDetails();
return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
oAuth2AuthenticationDetails.getTokenValue()));
} else {
return Optional.empty();
}
}
}
and then, declare a config class for your feign client using your RequestInterceptor, it should contains something like this :
#Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
return new MyRequestInterceptor();
}
Your Feign client shoud look like this:
#FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {
I had the same issue with Feign Client running on startup using ApplicationRunner and I came up with following solution.
I defined my FeignClientsConfiguration with OAuth2FeignRequestInterceptor, which accepts predefined bean DefaultOAuth2ClientContext and OAuth2 configuration OAuth2ProtectedResourceDetails:
#Configuration
public class MyConfig extends FeignClientsConfiguration {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor( DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
}
#Bean
public DefaultOAuth2ClientContext oAuth2ClientContext() {
return new DefaultOAuth2ClientContext();
}
private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
resourceDetails.setUsername(oauth2Properties.getUsername());
resourceDetails.setPassword(oauth2Properties.getPassword());
resourceDetails.setClientId(oauth2Properties.getClientId());
return resourceDetails;
}
}
Your feign client will look something like this:
#FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}
After all this, calling FeignClient from ApplicationRunner.run() works fine.
Spring Boot 2.2.6
Preface
First of all, my sincerest apologies for this question being extremely long, but I honestly have no idea on how to shorten it, since each part is kind of a special case. Admittedly, I may be blind on this since I am banging my head against the wall for a couple of days now and I am starting to get desperate.
My utmost respect and thankfulness to all of you who read through it.
The aim
I would like to be able to map Shiro's AuthenticationException and it's subclasses to JAX-RS Responses by using Jersey ExceptionMappers, set up using a Guice 3.0 Injector which creates an embedded Jetty.
The environment
Guice 3.0
Jetty 9.2.12.v20150709
Jersey 1.19.1
Shiro 1.2.4
The setup
The embedded Jetty is created using a Guice Injector
// imports omitted for brevity
public class Bootstrap {
public static void main(String[] args) throws Exception {
/*
* The ShiroWebModule is passed as a class
* since it needs a ServletContext to be initialized
*/
Injector injector = Guice.createInjector(new ServerModule(MyShiroWebModule.class));
Server server = injector.getInstance(Server.class);
server.start();
server.join();
}
}
The ServerModule binds a Provider for the Jetty Server:
public class ServerModule extends AbstractModule {
Class<? extends ShiroWebModule> clazz;
public ServerModule(Class <?extends ShiroWebModule> clazz) {
this.clazz = clazz;
}
#Override
protected void configure() {
bind(Server.class)
.toProvider(JettyProvider.withShiroWebModule(clazz))
.in(Singleton.class);
}
}
The JettyProvider sets up a Jetty WebApplicationContext, registers the ServletContextListener necessary for Guice and a few things more, which I left in to make sure no "side effects" may be hidden:
public class JettyProvider implements Provider<Server>{
#Inject
Injector injector;
#Inject
#Named("server.Port")
Integer port;
#Inject
#Named("server.Host")
String host;
private Class<? extends ShiroWebModule> clazz;
private static Server server;
private JettyProvider(Class<? extends ShiroWebModule> clazz){
this.clazz = clazz;
}
public static JettyProvider withShiroWebModule(Class<? extends ShiroWebModule> clazz){
return new JettyProvider(clazz);
}
public Server get() {
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
// Set during testing only
webAppContext.setResourceBase("src/main/webapp/");
webAppContext.setParentLoaderPriority(true);
webAppContext.addEventListener(
new MyServletContextListener(injector,clazz)
);
webAppContext.addFilter(
GuiceFilter.class, "/*",
EnumSet.allOf(DispatcherType.class)
);
webAppContext.setThrowUnavailableOnStartupException(true);
QueuedThreadPool threadPool = new QueuedThreadPool(500, 10);
server = new Server(threadPool);
ServerConnector connector = new ServerConnector(server);
connector.setHost(this.host);
connector.setPort(this.port);
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(new NCSARequestLog());
HandlerCollection handlers = new HandlerCollection(true);
handlers.addHandler(webAppContext);
handlers.addHandler(requestLogHandler);
server.addConnector(connector);
server.setStopAtShutdown(true);
server.setHandler(handlers);
return server;
}
}
In MyServletContextListener, I created a child injector, which gets initialized with the JerseyServletModule:
public class MyServletContextListener extends GuiceServletContextListener {
private ServletContext servletContext;
private Injector injector;
private Class<? extends ShiroWebModule> shiroModuleClass;
private ShiroWebModule module;
public ServletContextListener(Injector injector,
Class<? extends ShiroWebModule> clazz) {
this.injector = injector;
this.shiroModuleClass = clazz;
}
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
this.servletContext = servletContextEvent.getServletContext();
super.contextInitialized(servletContextEvent);
}
#Override
protected Injector getInjector() {
/*
* Since we finally have our ServletContext
* we can now instantiate our ShiroWebModule
*/
try {
module = shiroModuleClass.getConstructor(ServletContext.class)
.newInstance(this.servletContext);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
/*
* Now, we create a child injector with the JerseyModule
*/
Injector child = injector.createChildInjector(module,
new JerseyModule());
SecurityManager securityManager = child
.getInstance(SecurityManager.class);
SecurityUtils.setSecurityManager(securityManager);
return child;
}
}
The JerseyModule, a subclass of JerseyServletModule now put everything together:
public class JerseyModule extends JerseyServletModule {
#Override
protected void configureServlets() {
bindings();
filters();
}
private void bindings() {
bind(DefaultServlet.class).asEagerSingleton();
bind(GuiceContainer.class).asEagerSingleton();
serve("/*").with(DefaultServlet.class);
}
private void filters() {
Map<String, String> params = new HashMap<String, String>();
// Make sure Jersey scans the package
params.put("com.sun.jersey.config.property.packages",
"com.example.webapp");
params.put("com.sun.jersey.config.feature.Trace", "true");
filter("/*").through(GuiceShiroFilter.class,params);
filter("/*").through(GuiceContainer.class, params);
/*
* Although the ExceptionHandler is already found by Jersey
* I bound it manually to be sure
*/
bind(ExceptionHandler.class);
bind(MyService.class);
}
}
The ExceptionHandler is extremely straightforward and looks like this:
#Provider
#Singleton
public class ExceptionHandler implements
ExceptionMapper<AuthenticationException> {
public Response toResponse(AuthenticationException exception) {
return Response
.status(Status.UNAUTHORIZED)
.entity("auth exception handled")
.build();
}
}
The problem
Now everything works fine when I want to access a restricted resource and enter correct principal/credential combinations. But as soon as enter a non-existing user or a wrong password, I want an AuthenticationException to be thrown by Shiro and I want it to be handled by the above ExceptionHandler.
Utilizing the default AUTHC filter provided by Shiro in the beginning, I noticed that AuthenticationExceptions are silently swallowed and the user is redirected to the login page again.
So I subclassed Shiro's FormAuthenticationFilter to throw an AuthenticationException if there is one:
public class MyFormAutheticationFilter extends FormAuthenticationFilter {
#Override
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request,
ServletResponse response) {
if(e != null){
throw e;
}
return super.onLoginFailure(token, e, request, response);
}
}
And I also tried it with throwing the exception e wrapped in a MappableContainerException.
Both approaches cause the same problem: Instead of the exception being handled by the defined ExceptionHandler, a javax.servlet.ServletException is thrown:
javax.servlet.ServletException: org.apache.shiro.authc.AuthenticationException: Unknown Account!
at org.apache.shiro.web.servlet.AdviceFilter.cleanup(AdviceFilter.java:196)
at org.apache.shiro.web.filter.authc.AuthenticatingFilter.cleanup(AuthenticatingFilter.java:155)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:148)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.shiro.guice.web.SimpleFilterChain.doFilter(SimpleFilterChain.java:41)
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:163)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.shiro.authc.AuthenticationException: Unknown Account!
at com.example.webapp.security.MyAuthorizingRealm.doGetAuthenticationInfo(MyAuthorizingRealm.java:27)
at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:568)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
at org.apache.shiro.web.filter.authc.AuthenticatingFilter.executeLogin(AuthenticatingFilter.java:53)
at org.apache.shiro.web.filter.authc.FormAuthenticationFilter.onAccessDenied(FormAuthenticationFilter.java:154)
at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)
at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
... 32 more
The question, after all
Given that the environment can't be changed, how can I achieve that a server instance still can be requested via Guice, while Shiro's exceptions are handled with Jersey's auto discovered ExceptionMappers?
This question is much too complicated for me to reproduce on my side, but I saw a problem that I think is the answer and I'll delete this answer if I turn out to be wrong.
You do this:
#Provider
#Singleton
public class ExceptionHandler implements
ExceptionMapper<AuthenticationException> {
Which is correct, you are supposed to bind with both of those annotations as in this question. However, what you do differently is this:
/*
* Although the ExceptionHandler is already found by Jersey
* I bound it manually to be sure
*/
bind(ExceptionHandler.class);
The annotations in a class definition have lower priority than that in a module's configure() method, meaning you are erasing the annotations when you bind "it manually just to be sure". Try erasing that line of code and see if that fixes your problem. If it doesn't fix the problem, leave it deleted anyway, because I am certain that it is at least part of the problem - that statement erases those essential annotations.
I've not found a way to do this either. It looks like the Jersey filters/handlers aren't active on the Shiro servlet stack during authentication. As a work-around specifically for the AuthenticationException I opted to override the AdviceFilter::cleanup(...) method on my AuthenticatingFilter and return a custom message directly.
public class MyTokenAuthenticatingFilter extends AuthenticatingFilter {
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
// regular auth/token creation
}
#Override
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
HttpServletResponse httpResponse = (HttpServletResponse)response;
if ( null != existing ) {
httpResponse.setContentType(MediaType.APPLICATION_JSON);
httpResponse.getOutputStream().write(String.format("{\"error\":\"%s\"}", existing.getMessage()).getBytes());
httpResponse.setStatus(Response.Status.FORBIDDEN.getStatusCode());
existing = null; // prevent Shiro from tossing a ServletException
}
super.cleanup(request, httpResponse, existing);
}
}
When authentication is successful the ExceptionMappers work fine for exceptions thrown within the context of the Jersey controllers.
I am trying to use Quartz 2.2.0 with spring 3.2.x, using ServletContextListener for listening FileChangeListener class.Is My importManagerService object is null? Any suggestions? Not getting how to resolve it
Error While deploying
INFO [2013-10-04 15:13:16.009] [localhost-startStop-1]: org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
ERROR [2013-10-04 15:13:16.061] [DefaultQuartzScheduler_Worker-1]: org.quartz.core.JobRunShell - Job g1.t1 threw an unhandled Exception:
java.lang.NullPointerException at
com.demo.portal.web.importExportFile.ScheduleImportFile.execute(ScheduleImportFile.java:40)
at org.quartz.core.JobRunShell.run(JobRunShell.java:207)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:560)
ERROR [2013-10-04 15:13:16.065] [DefaultQuartzScheduler_Worker-1]: org.quartz.core.ErrorLogger - Job (g1.t1 threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.NullPointerException]
at org.quartz.core.JobRunShell.run(JobRunShell.java:218)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:560)
Caused by: java.lang.NullPointerException
at com.happiestminds.portal.web.importExportFile.ScheduleImportFile.execute(ScheduleImportFile.java:40)
at org.quartz.core.JobRunShell.run(JobRunShell.java:207)
... 1 more
FileChangeListener
public class FileChangeListener implements ServletContextListener {
private static final Logger logger = Logger.getLogger(FileChangeListener.class);
#Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("Stopping Application successfully");
}
#Override
public void contextInitialized(ServletContextEvent arg0) {
logger.info("Initializing Application successfully..........");
JobDetail job = JobBuilder.newJob(ScheduleImportFile.class).withIdentity("t1", "g1").build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "g1").startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(60).repeatForever()).forJob("t1", "g1").build();
SchedulerFactory schFactory = new StdSchedulerFactory();
Scheduler sch;
try {
sch = schFactory.getScheduler();
sch.start();
sch.scheduleJob(job, trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
ScheduleImportFile
**public class ScheduleImportFile implements Job{
#Autowired
ImportManagerService importManagerService;
#Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
//some logic for reading and parsing files
Line no. 40
Map<String, List<File>> fileToBeProcessMap = importManagerService.getFilesInfo(config);
Config is object of Configuration class
}**
Web.xml
<listener>
<listener-class>com.demo.portal.web.importExportFile.FileChangeListener</listener-class>
</listener>
As you identified, We can not auto wire of Spring beans inside Quartz job as Spring Bean's life cycle is forbidden in side a job class.
But we can get those spring beans through a simple way without loading Spring bean xml again.
Here is it.
public class MyJob Implements Job
{
private MyBean myBean;
#Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
getBeansFromContext(context);
mybean.doSomeThing();
}
private void getBeansFromContext(JobExecutionContext context) throws SchedulerException
{
ApplicationContext applicationContext = (ApplicationContext)context.getScheduler().getContext().get("applicationContext");
this.mybean=applicationContext.getBean(MyBean.class);
}
}
You should have your schedulerFactoryBean configired in your beans.xml.
<beans:bean id="schedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<beans:property name="jobFactory">
<beans:bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory"></beans:bean>
</beans:property>
...
<beans:property name="applicationContextSchedulerContextKey"
value="applicationContext" /> -- Here is the guy!!
Hope this helps you.
I am developing a SOAP web service with CXF 2.3.3. I want to throw a custom exception when user has submitted wrong data. My exception class looks like this.
#WebFault(name="InvalidUserDataException", targetNamespace="http://foo.bar.com/")
#XmlAccessorType(XmlAccessType.FIELD)
public class InvalidUserDataException extends RuntimeException {
public InvalidUserDataException(){
super();
}
String validationErrors;
}
In my method Impl class I purposely throw this exception to see whether SoapFault exception is returned by CXF to client, but whatever I do to any of these classes results in same error: createNewUser has thrown exception, unwinding now.
Note that WSUserRegistration interface also declares itself that createNewUser method throws InvalidUserDataException.
public class WSUserRegistrationImpl implements WSUserRegistration {
#Autowired
IWSUserValidationProxy validationProxyImpl;
#Override
public int createNewUser(RegistrationInputProperty registrationInfo) throws InvalidUserDataException {
if (true) {
throw new InvalidUserDataException();
}
return 1;
}
}
My goal is to catch this exception from a SOAPFaultOutInterceptor and return a customized faultDetail.
How do I make CXF catch this error and return a SOAPFault object? Any ideas?
this would help you.
http://willemjiang.blogspot.com/2011/01/how-to-map-soap-fault-message-with.html