Creating a REST Service with Glassfish 4 outside of a WAR - rest

I have a rest service implemented as follows
#Stateless
#Path("/Person")
#Produces(MediaType.APPLICATION_XML)
public class RestService {
#PersistenceContext(unitName = "mysqlPU")
EntityManager em;
#GET
#Path("{id}")
public Response Book(#PathParam("id") Long id) {
Person person = em.find(Person.class, id);
if(person == null) {
return Response.status(Response.Status.FORBIDDEN).build();
}
return Response.ok(person).build();
}
}
#ApplicationPath("/rs")
public class ApplicationConfig extends Application {
private final Set<Class<?>> classes;
public ApplicationConfig() {
HashSet<Class<?>> c = new HashSet<Class<?>>();
c.add(RestService.class);
classes = Collections.unmodifiableSet(c);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
}
It is packaged in a jar file with other ejbs, outside of any .war. I'm am only able to access the service if I happen to have a war package inside the ear. I can access it with the context root of the war such as localhost:8080/warContextRoot/rs/. Is there any way to deploy a rest service without also deploying a war and still access it?

I think the reason is that you implemented the REST web service within an EJB session bean and EJBs are typically bundled in EAR files.
An alternative is that you can implement the REST web service as a regular Servlet, which serves requests through its standard GET/POST methods, then it can be deployed in a WAR. And you can invoke the REST web service through an URL defined in servlet mapping of web.xml.
Hope this helps.

Related

Swagger UI does not list any of the controller/end points though I am able to see the json under v2/api-docs endpoint

I am not able to get my Swagger UI to work with my project. Swagger UI comes up fine but it does not list any of my REST controllers.
I am using SPRING 4.2.6.RELEASE and Swagger 2.5.0 . My rest services are deployed to Tomcat 7.0.54 .
When Tomcat 7.0.54 comes up, it is able to fetch the swagger end points.
I am able to hit the endpoint v2/api-docs that fetches the json messages.
I am also able to hit the swagger-ui but I dont see any controllers listed.
The dropdowns are empty, as below
**The issue I am facing currently is that
I am not able to fetch the /swagger-resources/configuration/ui, when I launch the swagger UI I get 404 (Not Found) errror while the UI is trying to fetch /swagger-resources/configuration/ui . I have setup resource handlers for swagger-resources, but that does not seem to help. Can you please let me know what could be missing?
Should I be seeing resources folder under META-INF in my expanded WAR? Should there be any springfox related files/folder inside META-INF?
**
Maven dependency for Swagger
io.springfox
springfox-swagger2
2.5.0
io.springfox
springfox-swagger-ui
2.5.0
Below is my SwaggerCongifuration
#EnableSwagger2
public class SwaggerConfiguration {
#Bean
public Docket api() {
List<SecurityContext> security = new ArrayList<SecurityContext>();
security.add(securityContext());
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.pathMapping("/").securityContexts(security);
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.forPaths(PathSelectors.regex("/"))
.build();
}
}
Below is my WebConfig.xml
#EnableWebMvc
#Configuration
#Import(SwaggerConfiguration.class)
#ComponentScan("com.bank.direct.services")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> pConverters) {
pConverters.add(RestUtils.getJSONMessageConverter());
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Below is the SecurityCongif.xml
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationService _authenticationService;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder pAuth) throws Exception {
pAuth.userDetailsService(_authenticationService);
}
#Override
protected void configure(HttpSecurity pHttp) throws Exception {
// Enable HTTP caching
pHttp.headers().cacheControl().disable();
// Configure security
pHttp.httpBasic()
// -- Allow only authenticated request
.and()
.authorizeRequests()
.anyRequest().authenticated()
// -- Logout configuration
.and()
.logout()
.logoutUrl("/rest/users/logout/")
.deleteCookies("XSRF-TOKEN")
.logoutSuccessUrl("/static/index.html")
.invalidateHttpSession(true)
// -- CSRF configuration
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
Rest Controller class as below
#RestController
#RequestMapping(value = "/vehicles", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class VehicleResource extends Resource {
#Autowired
private IVehicleService _vehicleService;
#RequestMapping(value = "/brands", method = RequestMethod.GET)
public APIResponseEntity getBrands(WebRequest pWebRequest) {
IUser user = getUser(pWebRequest);
BrandCriteria criteria = new BrandCriteria();
criteria.setLanguageCode(user.getLanguageCode());
List<Brand> res = _vehicleService.getBrands(user, criteria);
return newResponseOK(res);
}
#RequestMapping(value = "/brands/{brand_code}", method = RequestMethod.GET)
public APIResponseEntity getBrand(WebRequest pWebRequest, #PathVariable("brand_code") String pBrandCode) {
IUser user = getUser(pWebRequest);
BrandCriteria criteria = new BrandCriteria();
criteria.setLanguageCode(user.getLanguageCode());
criteria.setBrandCode(pBrandCode);
List<Brand> res = _vehicleService.getBrands(user, criteria);
return newResponseOK(res);
}
}
After migrating an older project from XML Spring configuration to Java Spring configuration and updating spring and Swagger versions I struggled with an issue that sounds exactly like this so I thought I'd document my solution here.
I had a number of problems but the main ones that match the OP's scenario were that while /v2/api-docs was accessible and returned JSON, my Controllers clearly weren't being picked up, and when I accessed the Swagger UI at /swagger-ui.html, I was getting a 404 when that page tried to request /swagger-resources/configuration/ui
My Swagger configuration class was:
#Configuration
#EnableSwagger2
public class SwaggerWebConfig {
#Bean
public Docket api() {
...
}
}
The #EnableSwagger2 annotation imports another configuration class Swagger2DocumentationConfiguration, which in turn imports SwaggerCommonConfiguration, which does a component scan for classes in springfox.documentation.swagger.web which finally loads the ApiResourceController, which is where
/swagger-resources/
/swagger-resources/configuration/security and
/swagger-resources/configuration/ui
are served from.
What I had incorrect was that my SwaggerWebConfig class was being loaded by the root application context, when it should belong to the web application context (see ApplicationContext vs WebApplicationContext).
Beans in the web application context can access beans in the root application context, but not the other way around, which explained why Docket bean (incorrectly in the root application context) could not pick up the #Controller beans in the web application context and also explained why despite the ApiResourceController bean being created, its methods were giving 404's when trying to access them (they should be in the web application context)
A few other notes for related issues:
If you can hit v2/api-docs then your Docket bean is working
In a non-spring-boot environment, you need to register two resource handlers yourself as spring boot's auto-configuration would have done this for you as explained in the answers to this question. That should solve 404's for:
/swagger-ui.html (i.e. 404 fetching the actual html swagger-ui.html page)
and the three webjars that swagger-ui.html loads:
/webjars/springfox-swagger-ui/springfox.js
/webjars/springfox-swagger-ui/swagger-ui-bundle.js
/webjars/springfox-swagger-ui/swagger-ui-standalone-preset.js
If you are getting an access denied rather than a 404 not found, then as shown in this answer, you might need to tell spring security to allow access to:
/webjars/**
/swagger-ui.html
/v2/api-docs
/swagger-resources/**
You need to point the the generated Swagger Definition in Swagger UI. i.e in place of http://example.com/api give your swagger definition path something like http://localhost:8080/RestResource/api/swagger.json
This article might help you more

Spring Security and custom ws authentication

We just went over to using Spring Boot and Spring Security for a new project that we have. The problem is, our company uses CXF with a custom implemented SAML authentication regime. Of course the custom implementation is rather old, so we are locked to CXF 2.7.*.
A while back, all worked fine since we only exposed SOAP web services and did not use Spring Security. The authenticator that we used in the old solution, uses some kind of JBossWebRealm thingy to authenticate through org.apache.catlina.connector.Request.
But now, we are going to expose REST services as well, using LDAP as an authentication provider. This works like a dream using Spring Security, but now, the security on SOAP services fails. It now tries to use Spring Security to authenticate using the SAML token as a password into AD.
Currently we have the default Servlet that Spring Boot creates. This one exposes the REST resources and a simple health check webpage.
Then we have a servlet that exposes SOAP web services and one that exposes metrics (REST).
Servlet setup:
#Configuration
#EnableAutoConfiguration
#Import(ApplicationConfig.class)
public class ApplicationServletInitializer extends SpringBootServletInitializer {
#Bean
public WebMvcConfigurerAdapter dispatcherServletConfigurer(final MDCInterceptor mdcInterceptor) {
return new WebMvcConfigurerAdapter() {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/internal/*");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(mdcInterceptor);
}
};
}
#Bean(name = "webServiceServlet")
public ServletRegistrationBean webServiceServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.setServlet(new CXFServlet());
servletRegistrationBean.setName("webServiceServlet");
servletRegistrationBean.addUrlMappings("/ws/*");
servletRegistrationBean.setLoadOnStartup(2);
return servletRegistrationBean;
}
#Bean(name = "metricsServlet")
public ServletRegistrationBean metricsServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.setServlet(new MetricsServlet());
servletRegistrationBean.setName("metricsServlet");
servletRegistrationBean.addUrlMappings("/internal/metrics/*");
servletRegistrationBean.setLoadOnStartup(3);
return servletRegistrationBean;
}
}
Security setup:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan(basePackageClasses = {
MDCInterceptor.class,
WebSecurityConfigurerAdapterConfig.class
})
public class RestSecurityConfig {
#Value("${ldap.url}")
private String ldapUrl;
#Value("${ldap.domain}")
private String ldapDomain;
#Bean
public ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapDomain, ldapUrl);
provider.setAuthoritiesMapper(authoritiesMapper());
provider.setUserDetailsContextMapper(userDetailsMapper());
provider.setUseAuthenticationRequestCredentials(true);
provider.setConvertSubErrorCodesToExceptions(true);
return provider;
}
#Bean
public MyAuthoritiesMapper authoritiesMapper() {
return new MyAuthoritiesMapper();
}
#Bean
public MyUserDetailsMapper userDetailsMapper() {
return new MyUserDetailsMapper();
}
}
#Component
public class WebSecurityConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ActiveDirectoryLdapAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers("/api/**").hasRole("READ")
.and().httpBasic()
.and().anonymous()
.principal(anonymousPrincipal())
.authorities(anonymousRoles());
}
}
web.xml setup:
<security-constraint>
<web-resource-collection>
<web-resource-name>All pages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
</security-constraint>
Does anyone know if it's possible to solve this? It's not an option to remove the usage of the old security framework that authenticates SAML for the SOAP web services.
Figured it out.
When configuring Spring Security, in WebSecurityConfigurerAdapter, you can also override:
protected void configure(WebSecurity web).
Within this one, you can specify what to ignore.
E.g.:
web.ignoring().antMatchers("/ws/**");

Injecting EJB into Rest Exception Handler

I'm trying to inject a local #Stateless EJB into a Rest exception handler but getting the following error.
javax.naming.NameNotFoundException: Name [Test] is not bound in this Context. Unable to find [Test].
The maven Web project is running on Apache-tomee-1.7.1-jaxrs.
The EJB:
#Stateless(name = "Test")
public class Test {
public void sayHello() {
System.out.println("Hello");
}
}
The Exception handler which from my understanding I must treat as a client to the EJB.
#Provider
public class TestExceptionHandler implements ExceptionMapper<Throwable> {
#Context
HttpServletRequest request;
#Override
public Response toResponse(Throwable throwable) {
InitialContext context;
try {
context = new InitialContext();
Test test = (Test) context.lookup("Test");
test.sayHello();
} catch (NamingException ex) {
ex.printStackTrace();
}
return Response.ok().build();
}
}
I have also tried to do the following for the lookup: context.lookup("java:comp/env/Test");
The http://openejb.apache.org/jndi-names.html documentation is very difficult to understand.
Also tried the following which was my first attempt. http://blog.iadvise.eu/2015/06/01/jee-using-ejb-and-context-annotations-in-a-jax-rs-provider-class/
Am I missing any configuration in the tomee server or in my code?
The java:comp/env namespace is for the EJB references, not EJBs. You have not declared an EJB reference anywhere.
It's probably easiest to directly look up the EJB using lookup("java:module/Test") (assuming the EJB is packaged in the war, otherwise, java:app/ejbmodname/Test) because JAX-RS does not support EE injection by default. To declare an EJB reference, you would need to make the provider class an EJB itself or a CDI class (add beans.xml to the module), and then declare a field as #EJB(name="Test") Test myBean;.

Jersey EJB injection

I read a lot about the possibility of injection with jax rs 2.0 and in particular with jersey.
I even read that ejb injection is expected in jax rs 2.0 spec. But i still haven't found a unique solution among the variety of posts i read over the net.
In my use case i'm working with:
WildFly 9.0 and Jersey 2.x
I have a webapplication exposing my REST services and importing a jar implementing my model data.
This is the CDI approach:
#RequestScoped
#Path("/myPath")
public class ModelRetriever {
#Context
SecurityContext securityContext;
#Inject
private IMyModel MyModel;
#Path("{i}")
#GET
#Produces("application/json")
public Response countries(#PathParam("i") String countryId)
throws JSONException, Failure, IOException {
MyModel.doSomething();
}
This is my IMyModel interface
public interface IKasPrincipal extends Principal {
public void doSomething();
}
And this is MyModel implementation:
#RequestScope
public class MyModelImpl implements IMyModel {
public void doSomehting() {
doSomething();
}
}
Another method i tried is to use EJB injection changing my previous annotations like this:
#Stateless
#Path("/myPath")
public class ModelRetriever {
#EJB
private IMyModel MyModel;
#Path("{i}")
#GET
#Produces("application/json")
public Response countries(#PathParam("i") String countryId)
throws JSONException, Failure, IOException {
MyModel.doSomething();
}
This is my IMyModel interface
#Local
public interface IKasPrincipal extends Principal {
public void doSomething();
}
And this is MyModel implementation:
#Stateless
public class MyModelImpl implements IMyModel {
public void doSomehting() {
doSomething();
}
}
i get a null object using EJB approach and i get this exception using CDI
Caused by: org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=IMyModel,parent=ModelRetriever,qualifiers={},position=-1,optional=false,self=false,unqualified=null,616459318)
at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:75)
at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:211)
at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:234)
So is there anything i'm missing?
see other Stack Overflow related posts:
Dependency injection with Jersey 2.0
HK2 Jersey EJB 3 injection
The problem you are having is that HK2 does not know about anything that was not registered directly into it, and HK2 tries to to satisfy all dependency in your Jersey aware class.
I has this issue a while back. Then I discovered that Jersey uses HK2 internally. HK2 is a JSR-330 implementation (CDI).
One would think that a open-source project would declares it's CDI beans and use them regardless of the CDI implementation, but it looks like its not that way.
see : https://java.net/jira/browse/JERSEY-1933
see : https://hk2.java.net/integration.html
You can register your components into HK2...
see : https://jersey.java.net/documentation/latest/ioc.html
For all I know, you cannot inject CDI components (or anything else, as EJB) into Jersey's classes using your own (or your container's) CDI implementation, unless you use Glassfish (which personally I would never use) which in turn uses HK2 as its CDI implementation.
To me, this is a major draw back. But the only(?) draw back of Jersey.
-Maybe I missed something (which is very possible)
-Maybe this is a trick from Oracle so that you can't use Jersey in, let's say, your Websphere app which uses OpenWeb Beans as CDI implementation.
-Maybe they hardwired it to HK2, and just don't care that Jersey can't be used as a drop in component in your application, which relies on CDI or EJB
I'm not aware of why #EJB not worked, but, you can use #Produces/#Disposes bean.
#ApplicationScoped // or some other scoped
public class MyModelProducer {
#Produces public MyModel produceMyModel() {
}
public void disposeMyModel#Disposes final MyModel model) {
}
}

PersistenceContext propagation and transaction spanning multiple EJBs in EJB 3.x

In our web application, we have a facade EJB which in turn calls multiple EJBs to perform a business function. The flow is like :
SLSB facade -> invokes ejb1, ejb2, ejb 3 etc -> invoke JPA layer
In each of the ejbs in the business layer, i inject entity manager using #PersistenceContext.
A simplified version of the code is as below:
#Stateless
public class facade{
#EJB
private EJB1 ejb1;
#EJB
private EJB2 ejb2;
#EJB ejb3;
private EJB3 ejb3;
public void performAction(..) {
// invoke method on ejb1
// invoke method on ejb2
// invoke method on ejb3
}
}
#Stateless
public class EJB1 implements IEjb1 {
#PersistenceContext(unitName = "pu")
private EntityManager entityManager;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public xxxEntity insert(xxxEntity entity) throws AppException {
// code for persisting the entity
}
}
#Stateless
public class EJB2 implements IEjb2 {
#PersistenceContext(unitName = "pu")
private EntityManager entityManager;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public yyyEntity insert(yyyEntity entity) throws AppException {
// code for persisting the entity
}
}
The application is deployed on Glassfish and uses JTA transaction. Since this is container managed PC, will the same persistence context be propagated to all EJBs? Will they run in the same transaction (reusing the transaction started in EJB1)? Is there a way to verify if the same transaction is used by all EJBs (same transaction id?)
By default all ejbs (1,2,3) will participate in the one, same transaction that is started by facade ejb. You would need to define different transaction attribute for them to use different transactions (e.g. REQIRES_NEW to create new transaction).
If you really need transaction id, you could try to inject TransactionSynchronizationRegistry into your bean like this
#Resource
TransactionSynchronizationRegistry registry;
// and then get key
...
registry.getTransactionKey()
See TransactionSynchronizationRegistry and Container-Managed Transactions