javax.ejb.EJBTransactionRolledbackException : For keycloak user spi provider - keycloak

Below is my pom.xml
<properties>
<keycloak.version>4.3.0.Final</keycloak.version>
<version.hibernate.javax.persistence>1.0.0.Final</version.hibernate.javax.persistence>
<version.jboss-ejb-api>1.0.0.Final</version.jboss-ejb-api>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.3.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>${version.hibernate.javax.persistence}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<version>${version.jboss-ejb-api}</version>
<scope>provided</scope>
</dependency>
</dependencies>
The exception that I am getting here is:
2018-09-08 16:36:12,303 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-4) SQL Error: 0, SQLState: null
2018-09-08 16:36:12,304 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-4) IJ031070: Transaction cannot proceed: STATUS_MARKED_ROLLBACK
2018-09-08 16:36:12,313 ERROR [org.jboss.as.ejb3.invocation] (default task-4) WFLYEJB0034: EJB Invocation failed on component NeemiyaUsersProvider for method public org.keycloak.models.UserModel com.neemiya.keycloak.userstoragespi.NeemiyaUsersProvider.getUserByUsername(java.lang.String,org.keycloak.models.RealmModel): javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: could not prepare statement
at org.jboss.as.ejb3.tx.CMTTxInterceptor.handleInCallerTx(CMTTxInterceptor.java:160)
at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:257)
at org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:334)
at org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:240)
at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
at org.jboss.as.ejb3.component.interceptors.CurrentInvocationContextInterceptor.processInvocation(CurrentInvocationContextInterceptor.java:41)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not prepare statement
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1602)
at org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.java:492)
at com.neemiya.keycloak.userstoragespi.NeemiyaUsersProvider.getUserByUsername(NeemiyaUsersProvider.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.jboss.as.ee.component.ManagedReferenceMethodInterceptor.processInvocation(ManagedReferenceMethodInterceptor.java:52)
at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
at org.jboss.as.ejb3.component.invocationmetrics.ExecutionTimeInterceptor.processInvocation(ExecutionTimeInterceptor.java:43)
at org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)

package com.neemiya.keycloak.userstoragespi;
import java.util.UUID;
import javax.ejb.Local;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.OnUserCache;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.user.UserLookupProvider;
import com.neemiya.keycloak.userstoragespi.entity.UserAccountMapping;
import com.neemiya.keycloak.userstoragespi.entity.UserCredentialEntity;
import com.neemiya.keycloak.userstoragespi.entity.UserEntity;
import com.neemiya.keycloak.userstoragespi.utils.Crypto;
#Stateful(passivationCapable = false)
#Local(NeemiyaUsersProvider.class)
public class NeemiyaUsersProvider
implements UserStorageProvider, UserLookupProvider, CredentialInputValidator, OnUserCache {
private static final Logger logger = Logger.getLogger(NeemiyaUsersProvider.class);
public static final String PASSWORD_CACHE_KEY = UserAdapter.class.getName() + ".password";
private static final String GET_USER_FOR_USERNAME = "select uc from UserCredentialEntity uc where uc.username=:username";
private static final String GET_USER_ACCOUNT_MAPPING_FOR_OWNER = "select uam from UserAccountMapping uam where uam.userId = :userId and"
+ " uam.requestedByUserId =:userId and uam.role = com.neemiya.keycloak.userstoragespi.enums.ROLE.owner and "
+ " uam.request_status = com.neemiya.keycloak.userstoragespi.enums.REQUEST_STATUS.completed";
#PersistenceContext
protected EntityManager em;
protected ComponentModel model;
protected KeycloakSession session;
public void setModel(ComponentModel model) {
this.model = model;
}
public void setSession(KeycloakSession session) {
this.session = session;
}
#Override
public void close() {
}
#Override
public UserModel getUserById(String id, RealmModel realm) {
logger.info("getUserById: " + id);
try {
String persistenceId = StorageId.externalId(id);
UUID persistenceUUID = UUID.fromString(persistenceId);
UserCredentialEntity credentialEntity = em.find(UserCredentialEntity.class, persistenceUUID);
if (credentialEntity == null) {
logger.info("could not find user by id: " + id);
return null;
}
TypedQuery<UserAccountMapping> uamQuery = em.createQuery(GET_USER_ACCOUNT_MAPPING_FOR_OWNER,
UserAccountMapping.class);
uamQuery.setParameter("userId", credentialEntity.getUserEntity().getUserId());
UserAccountMapping uam = uamQuery.getSingleResult();
credentialEntity.setAccountId(uam.getAccountId());
return new UserAdapter(session, realm, model, credentialEntity);
} catch (Exception e) {
logger.error("Couldn't fetch user by id " + e.getMessage());
}
return null;
}
#Override
public UserModel getUserByUsername(String username, RealmModel realm) {
logger.info("getUserByUsername: " + username);
try {
TypedQuery<UserCredentialEntity> query = em.createQuery(GET_USER_FOR_USERNAME, UserCredentialEntity.class);
query.setParameter("username", username);
UserCredentialEntity credentialEntity = query.getSingleResult();
if (credentialEntity == null) {
logger.error("could not find username: " + username);
return null;
}
UserEntity entity = credentialEntity.getUserEntity();
if (entity == null) {
logger.error("Couldn't fetch user for given username ");
return null;
}
TypedQuery<UserAccountMapping> uamQuery = em.createQuery(GET_USER_ACCOUNT_MAPPING_FOR_OWNER,
UserAccountMapping.class);
uamQuery.setParameter("userId", entity.getUserId());
UserAccountMapping uam = uamQuery.getSingleResult();
if (uam == null) {
logger.error("Couldn't find an owner mapping for this user ");
return null;
}
credentialEntity.setAccountId(uam.getAccountId());
logger.info("Fetch USerCredentialEntity :: " + credentialEntity.toString());
return new UserAdapter(session, realm, model, credentialEntity);
} catch (Exception e) {
logger.error("Couldn't validate user credentials " + e.getMessage());
}
return null;
}
#Override
public UserModel getUserByEmail(String email, RealmModel realm) {
return null;
}
#Override
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
String password = ((UserAdapter) delegate).getPassword();
if (password != null) {
user.getCachedWith().put(PASSWORD_CACHE_KEY, password);
}
}
#Override
public boolean supportsCredentialType(String credentialType) {
return CredentialModel.PASSWORD.equals(credentialType);
}
#Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return supportsCredentialType(credentialType) && getPassword(user) != null;
}
#Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel))
return false;
try {
UserCredentialModel cred = (UserCredentialModel) input;
String password = getPassword(user);
String encryptedPassword = Crypto.encryptSHA1(cred.getValue());
if (password != null) {
if (password.equals(encryptedPassword)) {
return true;
} else {
UserModel model = getUserByUsername(user.getUsername(), realm);
String currentPassword = getPassword(model);
boolean isPasswordValid = currentPassword.equals(encryptedPassword);
if (isPasswordValid) {
logger.info(
"It appears user has changed his password.Invalidating Cache and getting latest password from db");
((CachedUserModel) user).getCachedWith().put(PASSWORD_CACHE_KEY, currentPassword);
return isPasswordValid;
}
}
}
} catch (Exception e) {
logger.error("Couldn't validate user credentials " + e.getMessage());
}
return false;
}
public String getPassword(UserModel user) {
String password = null;
if (user instanceof CachedUserModel) {
password = (String) ((CachedUserModel) user).getCachedWith().get(PASSWORD_CACHE_KEY);
} else if (user instanceof UserAdapter) {
password = ((UserAdapter) user).getPassword();
}
return password;
}
public EntityManager getEm() {
return em;
}
public void setEm(EntityManager em) {
this.em = em;
}
}

Related

JERSEY: java.lang.IllegalStateException: The output stream has already been closed

I can access the data from browser using "localhost". But if I mention my system IP address the app is showing below error:
SEVERE: An I/O error has occurred while writing a response message entity to the container output stream.
java.lang.IllegalStateException: The output stream has already been closed.
at org.glassfish.jersey.message.internal.CommittingOutputStream.setStreamProvider(CommittingOutputStream.java:142)
at org.glassfish.jersey.message.internal.OutboundMessageContext.setStreamProvider(OutboundMessageContext.java:812)
at org.glassfish.jersey.server.ContainerResponse.setStreamProvider(ContainerResponse.java:373)
at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:645)
at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395)
at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:280)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:272)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:268)
at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
at org.glassfish.jersey.internal.Errors.process(Errors.java:268)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703)
at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:494)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:412)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:754)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1385)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
This is my AuthenticationFilter class:
package com.howtodoinjava.jersey.provider;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.internal.util.Base64;
/**
* This filter verify the access permissions for a user
* based on username and passowrd provided in request
* */
#Provider
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter
{
#Context
private ResourceInfo resourceInfo;
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final String AUTHENTICATION_SCHEME = "Basic";
private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED)
.entity("You cannot access this resource").build();
private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN)
.entity("Access blocked for all users !!").build();
#Override
public void filter(ContainerRequestContext requestContext)
{
Method method = resourceInfo.getResourceMethod();
//Access allowed for all
if( ! method.isAnnotationPresent(PermitAll.class))
{
//Access denied for all
if(method.isAnnotationPresent(DenyAll.class))
{
requestContext.abortWith(ACCESS_FORBIDDEN);
return;
}
//Get request headers
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
//Fetch authorization header
final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
//If no authorization information present; block access
if(authorization == null || authorization.isEmpty())
{
requestContext.abortWith(ACCESS_DENIED);
return;
}
//Get encoded username and password
final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
//Decode username and password
String usernameAndPassword = new String(Base64.decode(encodedUserPassword.getBytes()));;
//Split username and password tokens
final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
final String username = tokenizer.nextToken();
final String password = tokenizer.nextToken();
//Verifying Username and password
System.out.println(username);
System.out.println(password);
//Verify user access
if(method.isAnnotationPresent(RolesAllowed.class))
{
RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));
//Is user valid?
if( ! isUserAllowed(username, password, rolesSet))
{
requestContext.abortWith(ACCESS_DENIED);
return;
}
}
}
}
private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet)
{
boolean isAllowed = false;
//Step 1. Fetch password from database and match with password in argument
//If both match then get the defined role for user from database and continue; else return isAllowed [false]
//Access the database and do this part yourself
//String userRole = userMgr.getUserRole(username);
if(username.equals("howtodoinjava") && password.equals("password"))
{
String userRole = "ADMIN";
//Step 2. Verify user role
if(rolesSet.contains(userRole))
{
isAllowed = true;
}
}
return isAllowed;
}
}
This is my JerseyService class
package com.howtodoinjava.jersey.provider;
import java.util.ArrayList;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/employees")
public class JerseyService
{
#SuppressWarnings("unchecked")
#RolesAllowed("ADMIN")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Employees getAllEmployees()
{
Employees list = new Employees();
list.setEmployeeList(new ArrayList<Employees>());
list.getEmployeeList().add(new Employees(1, "Lokesh Gupta"));
list.getEmployeeList().add(new Employees(2, "Alex Kolenchiskey"));
list.getEmployeeList().add(new Employees(3, "David Kameron"));
return list;
}
}
This is my GsonMessageBodyHandler class:
package com.howtodoinjava.jersey.provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonMessageBodyHandler implements MessageBodyWriter<Object>,
MessageBodyReader<Object> {
private static final String UTF_8 = "UTF-8";
private Gson gson;
//Customize the gson behavior here
private Gson getGson() {
if (gson == null) {
final GsonBuilder gsonBuilder = new GsonBuilder();
gson = gsonBuilder.disableHtmlEscaping()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setPrettyPrinting()
.serializeNulls()
.create();
}
return gson;
}
#Override
public boolean isReadable(Class<?> type, Type genericType,
java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public Object readFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) {
InputStreamReader streamReader = null;
try {
streamReader = new InputStreamReader(entityStream, UTF_8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
Type jsonType;
if (type.equals(genericType)) {
jsonType = type;
} else {
jsonType = genericType;
}
return getGson().fromJson(streamReader, jsonType);
} finally {
try {
streamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public long getSize(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public void writeTo(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8);
try {
Type jsonType;
if (type.equals(genericType)) {
jsonType = type;
} else {
jsonType = genericType;
}
getGson().toJson(object, jsonType, writer);
} finally {
writer.close();
}
}
}
I could not identifying the issue. Please help to resolve the above error.
The problem is the use of a static Response
private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).entity("You cannot access this resource").build();
The first time you get no error but trying to access the resource a second time trows the error you mentioned. The error has nothing to do with using localhost or the IP of the related host. So modify the code in your AuthenticationFilter like so:
#Provider
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter
{
#Context
private ResourceInfo resourceInfo;
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final String AUTHENTICATION_SCHEME = "Basic";
#Override
public void filter(ContainerRequestContext requestContext)
{
Method method = resourceInfo.getResourceMethod();
//Access allowed for all
if( ! method.isAnnotationPresent(PermitAll.class))
{
//Access denied for all
if(method.isAnnotationPresent(DenyAll.class))
{
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Access blocked for all users !!").build(););
return;
}
//Get request headers
final MultivaluedMap<String, String> headers = equestContext.getHeaders();
//Fetch authorization header
final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
//If no authorization information present; block access
if(authorization == null || authorization.isEmpty())
{
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("You cannot access this resource").build(););
return;
}
//Get encoded username and password
final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
//Decode username and password
String usernameAndPassword = new String(Base64.decode(encodedUserPassword.getBytes()));;
//Split username and password tokens
final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
final String username = tokenizer.nextToken();
final String password = tokenizer.nextToken();
//Verifying Username and password
System.out.println(username);
System.out.println(password);
//Verify user access
if(method.isAnnotationPresent(RolesAllowed.class))
{
RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));
//Is user valid?
if( ! isUserAllowed(username, password, rolesSet))
{
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("You cannot access this resource").build(););
return;
}
}
}
}
and your authentication will work smoothly.

Tomcat 8.52 version CsrfPreventionFilter entryPoints param with regex pattern

I am using tomcat 8.52 to fix CSRF issue. In that
am using org.apache.catalina.filters.CsrfPreventionFilter.
How can I use entryPoints param with regex pattern matching.
How I can avoid CSRF checking in my login page.
My login page loads 20 js,40 imags,23 css. How all are can I mention in the entrypoint param?
My web.xml:
<filter>
<filter-name>CsrfFilter</filter-name>
<filter-class>org.apache.catalina.filters.CsrfPreventionFilter</filter-class>
<init-param>
<param-name>denyStatus</param-name>
<param-value>404</param-value>
</init-param>
<init-param>
<param-name>entryPoints</param-name>
<param-value>/mUser/login,/js/encrypt.js,/js/json-min.js,/m User/homepage,/dispatch/sendtemplate</param-value>
</init-param>
When I try to login with my pages, I am seeing only encrypt.js,json-min.js only loaded others are showing 404 error.
Also getting 404 page while logging to the page.
I define my own CsrfPreventionFilter class like this and put my JS and CSS and img in a folder named "static"
package filters.myCatalina;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
public class MyCsrfPreventionFilter extends MyCsrfPreventionFilterBase {
private Logger logger = Logger.getLogger(getClass().getName());
private final Set<String> entryPoints = new HashSet<>();
private int nonceCacheSize = 5;
public void setEntryPoints(String entryPoints) {
String values[] = entryPoints.split(",");
for (String value : values) {
this.entryPoints.add(value.trim());
}
}
public void setNonceCacheSize(int nonceCacheSize) {
this.nonceCacheSize = nonceCacheSize;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletResponse wResponse = null;
if (request instanceof HttpServletRequest &&
response instanceof HttpServletResponse) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
boolean skipNonceCheck = false;
logger.info(getRequestedPath(req));
if ("/".equals(getRequestedPath(req)))
skipNonceCheck = true;
if ("/static/".equals(getRequestedPath(req).substring(0, 8)))
skipNonceCheck = true;
if (MyConstants.METHOD_GET.equals(req.getMethod())
&& entryPoints.contains(getRequestedPath(req))) {
skipNonceCheck = true;
}
HttpSession session = req.getSession(false);
#SuppressWarnings("unchecked")
LruCache<String> nonceCache = (session == null) ? null
: (LruCache<String>) session.getAttribute(
MyConstants.CSRF_NONCE_SESSION_ATTR_NAME);
if (!skipNonceCheck) {
String previousNonce =
req.getParameter(MyConstants.CSRF_NONCE_REQUEST_PARAM);
if (nonceCache == null || previousNonce == null ||
!nonceCache.contains(previousNonce)) {
res.sendError(getDenyStatus());
return;
}
}
if (nonceCache == null) {
nonceCache = new LruCache<>(nonceCacheSize);
if (session == null) {
session = req.getSession(true);
}
session.setAttribute(
MyConstants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
}
String newNonce = generateNonce();
nonceCache.add(newNonce);
wResponse = new CsrfResponseWrapper(res, newNonce);
} else {
wResponse = response;
}
chain.doFilter(request, wResponse);
}
protected static class CsrfResponseWrapper
extends HttpServletResponseWrapper {
private final String nonce;
public CsrfResponseWrapper(HttpServletResponse response, String nonce) {
super(response);
this.nonce = nonce;
}
#Override
#Deprecated
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
#Override
public String encodeRedirectURL(String url) {
return addNonce(super.encodeRedirectURL(url));
}
#Override
#Deprecated
public String encodeUrl(String url) {
return encodeURL(url);
}
#Override
public String encodeURL(String url) {
return addNonce(super.encodeURL(url));
}
/*
* Return the specified URL with the nonce added to the query string.
*/
private String addNonce(String url) {
if (url == null) {
return nonce;
}
String path = url;
String query = "";
String anchor = "";
int pound = path.indexOf('#');
if (pound >= 0) {
anchor = path.substring(pound);
path = path.substring(0, pound);
}
int question = path.indexOf('?');
if (question >= 0) {
query = path.substring(question);
path = path.substring(0, question);
}
StringBuilder sb = new StringBuilder(path);
if (query.length() > 0) {
sb.append(query);
sb.append('&');
} else {
sb.append('?');
}
sb.append(MyConstants.CSRF_NONCE_REQUEST_PARAM);
sb.append('=');
sb.append(nonce);
sb.append(anchor);
return sb.toString();
}
}
protected static class LruCache<T> implements Serializable {
private static final long serialVersionUID = 1L;
// Although the internal implementation uses a Map, this cache
// implementation is only concerned with the keys.
private final Map<T, T> cache;
public LruCache(final int cacheSize) {
cache = new LinkedHashMap<T, T>() {
private static final long serialVersionUID = 1L;
#Override
protected boolean removeEldestEntry(Map.Entry<T, T> eldest) {
if (size() > cacheSize) {
return true;
}
return false;
}
};
}
public void add(T key) {
synchronized (cache) {
cache.put(key, null);
}
}
public boolean contains(T key) {
synchronized (cache) {
return cache.containsKey(key);
}
}
}
}
my web.xml config
<filter>
<filter-name>CSRF</filter-name>
<filter-class>filters.myCatalina.MyCsrfPreventionFilter</filter-class>
<init-param>
<param-name>entryPoints</param-name>
<param-value>/index.jsp,/,index.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CSRF</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
and in jsp entryPoint
<INPUT type="hidden" name="CSRF_NONCE" value="<%=response.encodeUrl(null)%>">
It's just a sample but it works and if you're using tomcat-catalina dependency you should use "org.apache.catalina.filters.CSRF_NONCE" in org.apache.catalina.filters.Constants class instead of "CSRF_NONCE"

Connection between Bluemix's Liberty for Java and Cloudant throw RuntimeError everyday

I create an application by using Bluemix's Liberty for Java to connect Cloudant. I found RuntimeException everyday while connecting to my RESTful API. However, I have to restart my application everyday to solve this problem.
An error is shown as follow:
Exception thrown by application class 'org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage:116'
java.lang.RuntimeException: org.apache.cxf.interceptor.Fault: Error retrieving server response
at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:116)
at [internal classes]
Caused by: org.apache.cxf.interceptor.Fault: Error retrieving server response
at org.apache.cxf.service.invoker.AbstractInvoker.createFault(AbstractInvoker.java:163)
... 1 more
Caused by: com.cloudant.client.org.lightcouch.CouchDbException: Error retrieving server response
at com.cloudant.client.org.lightcouch.CouchDbClient.execute(CouchDbClient.java:501)
at com.cloudant.client.org.lightcouch.CouchDbClient.executeToInputStream(CouchDbClient.java:515)
at com.cloudant.client.api.Database.findByIndex(Database.java:361)
at com.cloudant.client.api.Database.findByIndex(Database.java:321)
at th.co.gosoft.rest.TopicService.getHotTopicList(TopicService.java:82)
at sun.reflect.GeneratedMethodAccessor34.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.ibm.ws.jaxrs20.server.LibertyJaxRsServerFactoryBean.performInvocation(LibertyJaxRsServerFactoryBean.java:636)
... 1 more
Caused by: java.net.ProtocolException: Server rejected operation
at sun.net.www.protocol.http.HttpURLConnection.expect100Continue(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(Unknown Source)
at com.ibm.net.ssl.www2.protocol.https.b.getOutputStream(Unknown Source)
at com.cloudant.http.HttpConnection.execute(HttpConnection.java:231)
at com.cloudant.client.org.lightcouch.CouchDbClient.execute(CouchDbClient.java:466)
... 9 more
I use this CloudantClientMgr as follow:
package th.co.gosoft.util;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map.Entry;
import java.util.Set;
import com.cloudant.client.api.ClientBuilder;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class CloudantClientMgr {
private static CloudantClient cloudant = null;
private static Database db = null;
private static String databaseName = "go10_db";
private static String url = "https://xxxxxxxxxx-bluemix.cloudant.com";
private static String user = "xxxxxxxxxx-bluemix";
private static String password = "password";
private static void initClient() {
if (cloudant == null) {
synchronized (CloudantClientMgr.class) {
if (cloudant != null) {
return;
}
try {
cloudant = createClient();
} catch (MalformedURLException e) {
throw new RuntimeException(e.getMessage(), e);
}
System.out.println("cloudant : " + cloudant.serverVersion());
}
}
}
private static CloudantClient createClient() throws MalformedURLException {
String VCAP_SERVICES = System.getenv("VCAP_SERVICES");
String serviceName = null;
CloudantClient client;
if (VCAP_SERVICES != null) {
System.out.println("VCAP_SERVICE");
JsonObject obj = (JsonObject) new JsonParser().parse(VCAP_SERVICES);
Entry<String, JsonElement> dbEntry = null;
Set<Entry<String, JsonElement>> entries = obj.entrySet();
for (Entry<String, JsonElement> eachEntry : entries) {
if (eachEntry.getKey().toLowerCase().contains("cloudant")) {
dbEntry = eachEntry;
break;
}
}
if (dbEntry == null) {
throw new RuntimeException("Could not find cloudantNoSQLDB key in VCAP_SERVICES env variable");
}
obj = (JsonObject) ((JsonArray) dbEntry.getValue()).get(0);
serviceName = (String) dbEntry.getKey();
System.out.println("Service Name - " + serviceName);
obj = (JsonObject) obj.get("credentials");
user = obj.get("username").getAsString();
password = obj.get("password").getAsString();
client = ClientBuilder.account(user)
.username(user)
.password(password)
.build();
} else {
System.out.println("LOCAL");
client = ClientBuilder.url(new URL(url))
.username(user)
.password(password)
.build();
}
return client;
}
public static Database getDB() {
if (cloudant == null) {
initClient();
}
if (db == null) {
try {
db = cloudant.database(databaseName, true);
} catch (Exception e) {
throw new RuntimeException("DB Not found", e);
}
}
return db;
}
private CloudantClientMgr() {
}
}
I use this code to connect CloudantClientMgr as follow:
#Path("topic")
public class TopicService {
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON + ";charset=utf-8")
public Response createTopic(TopicModel topicModel) {
Database db = CloudantClientMgr.getDB();
com.cloudant.client.api.model.Response response = db.save(topicModel);
String result = response.getId();
return Response.status(201).entity(result).build();
}
}
The Cloudant version is 2.4.2
<dependency>
<groupId>com.cloudant</groupId>
<artifactId>cloudant-client</artifactId>
<version>2.4.2</version>
</dependency>
If anyone used to found this problem, please let me know the better solution than I have to restart my application every day.
The problem you describe sounds identical to one that was resolved in version 2.4.1.
I would check to make sure you don't have multiple versions of the cloudant-client included in your application or classpath and that your application is really using version 2.4.2. The line numbers in the stack trace you provide do not match with the source for 2.4.2.

Spring MVC: NullReferenceException when generating form

I'm having problems to figure out this problem. The exception is thrown when I try to go to /users/add url which should show the empty form for creating user entry. After that this exception is propagated throught the whole application.
Here is the controller code:
#Controller
#SessionAttributes(value = "user")
#RequestMapping(value = "/users")
public class UserController {
#Inject
private UserService userService;
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView getAllUsers() {
List<User> users = userService.getAllUsers();
return new ModelAndView("allUsers", "users", users);
}
#RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView addUser(ModelMap map, #RequestParam(value = "id", required = false) Long id) throws EntityNotFoundException {
User user = null;
if(id != null) {
user = userService.findById(id);
if(user == null)
throw new EntityNotFoundException("Can't find user!");
}
else {
user = new User();
user.setGender(Gender.MALE);
}
map.addAttribute("genders", generateGenders());
return new ModelAndView("addUser", "user", user);
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView addUser(ModelMap map, #Valid #ModelAttribute(value = "user") User user, BindingResult result,
HttpServletResponse response, final RedirectAttributes redirectAttributes) throws IOException {
if(!result.hasErrors()) {
try {
String userStatus = user.getId() != null ? "User Updated: " : "User Created: ";
userService.saveOrUpdateUser(user);
redirectAttributes.addFlashAttribute("userStatusMessage", userStatus + user.toString());
return new ModelAndView(new RedirectView("/users"));
} catch (EntityNotFoundException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
return null;
}
}
else {
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getObjectName() + ": " + error.getDefaultMessage());
}
}
map.addAttribute("genders", generateGenders());
return new ModelAndView("addUser", "user", user);
}
#RequestMapping(value = "/delete", method = RequestMethod.GET)
public ModelAndView deleteUser(#RequestParam(value = "id", required = true) Long id,
HttpServletResponse response, final RedirectAttributes redirectAttributes) throws IOException {
try {
User user = userService.deleteUser(id);
redirectAttributes.addFlashAttribute("userStatusMessage", "Deleted User: " + user);
} catch (EntityNotFoundException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
return null;
}
return new ModelAndView(new RedirectView("/users"));
}
private Map<String, String> generateGenders() {
Map<String,String> genders = new HashMap<String,String>();
for (Gender gender : Gender.values()) {
genders.put(gender.toString(), gender.getDisplayName());
}
return genders;
}
}
And here is the error I get when I go to /users/add:
HTTP ERROR 500
Problem accessing /users/add. Reason:
Server Error
Caused by:
java.lang.NullPointerException
at java.util.Calendar.setTime(Calendar.java:1106)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:955)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:948)
at java.text.DateFormat.format(DateFormat.java:336)
at com.code9.data.User.toString(User.java:157)
at java.lang.String.valueOf(String.java:2854)
at java.lang.StringBuilder.append(StringBuilder.java:128)
at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:146)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:263)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1208)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:992)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:939)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:533)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:475)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:514)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:920)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:403)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:184)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:856)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:247)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:151)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:114)
at org.eclipse.jetty.server.Server.handle(Server.java:352)
at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:596)
at org.eclipse.jetty.server.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:1049)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:590)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:212)
at org.eclipse.jetty.server.HttpConnection.handle(HttpConnection.java:426)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:510)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.access$000(SelectChannelEndPoint.java:34)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:40)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:450)
at java.lang.Thread.run(Thread.java:744)
Any suggestions?
I have found the problem. It's inside toString() method of User class. I'm still not shure when it's called, because I don't call it explicitly. Sorry for inconvenience.
This is User toString() method before:
#Override
public String toString() {
return String.format("%d, %s %s, %s, %s, %s, %s", this.getId(), this.getFirstName(), this.getLastName(),
new SimpleDateFormat("yyyy-MM-dd").format(this.getBirthday()), this.getGender().getDisplayName(), this.getPersonalNumber(),
this.getEmail());
}
And this is a fix:
#Override
public String toString() {
return String.format("%d, %s %s, %s, %s, %s, %s", this.getId(), this.getFirstName(), this.getLastName(),
this.getBirthday() == null ? "" : new SimpleDateFormat("yyyy-MM-dd").format(this.getBirthday()), this.getGender().getDisplayName(), this.getPersonalNumber(),
this.getEmail());
}

List all exposed/available endpoints of RestEasy service?

Is it possible to list all exposed/available endpoints of RestEasy service in a simple way?
There is a RestEasy plugin, "stats", which exposes .../resteasy/registry.
It needs to be registered in web.xml:
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value>
</context-param>
Example response:
<registry>
<resource uriTemplate="/resource">
<delete class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="delete"
invocations="0"/>
<head class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="head" invocations="0"/>
</resource>
<resource uriTemplate="/locator">
<locator class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="getLocator"/>
</resource>
<resource uriTemplate="/resteasy/registry">
<get class="org.jboss.resteasy.plugins.stats.RegistryStatsResource" method="get" invocations="2">
<produces>application/xml</produces>
<produces>application/json</produces>
</get>
</resource>
<resource uriTemplate="/entry/{foo:.*}">
<post class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="post" invocations="0">
<produces>text/xml</produces>
<consumes>application/json</consumes>
</post>
<put class="org.jboss.resteasy.test.providers.jaxb.resource.StatsResource" method="put" invocations="0">
<produces>text/xml</produces>
<consumes>application/json</consumes>
</put>
</resource>
</registry>
Maven dependency:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>3.0.8.Final</version>
</dependency>
See eg. EAP docs and this EAP 7 Jira
I had to adjust the "cleaner" example which was excellent to begin with. I'm using RestEasy 3.07 and wanted to also have each method's Path annotation value. I hope this modification can be of help to others.
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.ResourceInvoker;
import org.jboss.resteasy.core.ResourceMethodInvoker;
import org.jboss.resteasy.core.ResourceMethodRegistry;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
#Component
#Path("/overview")
public class OverviewResource
{
private static final class MethodDescription
{
private String method;
private String fullPath;
private String produces;
private String consumes;
public MethodDescription(String method, String fullPath, String produces, String consumes)
{
super();
this.method = method;
this.fullPath = fullPath;
this.produces = produces;
this.consumes = consumes;
}
}
private static final class ResourceDescription
{
private String basePath;
private List<MethodDescription> calls;
public ResourceDescription(String basePath)
{
this.basePath = basePath;
this.calls = Lists.newArrayList();
}
public void addMethod(String path, ResourceMethodInvoker method)
{
String produces = mostPreferredOrNull(method.getProduces());
String consumes = mostPreferredOrNull(method.getConsumes());
for (String verb : method.getHttpMethods())
{
calls.add(new MethodDescription(verb, path, produces, consumes));
}
}
private static String mostPreferredOrNull(MediaType[] mediaTypes)
{
if (mediaTypes == null || mediaTypes.length < 1)
{
return null;
}
else
{
return mediaTypes[0].toString();
}
}
public static List<ResourceDescription> fromBoundResourceInvokers(
Set<Map.Entry<String, List<ResourceInvoker>>> bound)
{
Map<String, ResourceDescription> descriptions = Maps.newHashMap();
for (Map.Entry<String, List<ResourceInvoker>> entry : bound)
{
Method aMethod = ((ResourceMethodInvoker) entry.getValue().get(0)).getMethod();
String basePath = aMethod.getDeclaringClass().getAnnotation(Path.class).value();
if (!descriptions.containsKey(basePath))
{
descriptions.put(basePath, new ResourceDescription(basePath));
}
for (ResourceInvoker invoker : entry.getValue())
{
ResourceMethodInvoker method = (ResourceMethodInvoker) invoker;
String subPath = null;
for(Annotation annotation : method.getMethodAnnotations())
{
if(annotation.annotationType().equals(Path.class))
{
subPath = ((Path) annotation).value();
break;
}
}
descriptions.get(basePath).addMethod(basePath + subPath, method);
}
}
return Lists.newLinkedList(descriptions.values());
}
}
#GET
#Path("/")
#Produces(MediaType.APPLICATION_JSON)
public List<ResourceDescription> getAvailableEndpoints(#Context Dispatcher dispatcher)
{
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
return ResourceDescription.fromBoundResourceInvokers(registry.getBounded().entrySet());
}
#GET
#Path("/")
#Produces(MediaType.TEXT_HTML)
public Response getAvailableEndpointsHtml(#Context Dispatcher dispatcher)
{
StringBuilder sb = new StringBuilder();
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
List<ResourceDescription> descriptions = ResourceDescription.fromBoundResourceInvokers(registry.getBounded()
.entrySet());
sb.append("<h1>").append("REST interface overview").append("</h1>");
for (ResourceDescription resource : descriptions)
{
sb.append("<h2>").append(resource.basePath).append("</h2>");
sb.append("<ul>");
for (MethodDescription method : resource.calls)
{
sb.append("<li> ").append(method.method).append(" ");
sb.append("<strong>").append(method.fullPath).append("</strong>");
sb.append("<ul>");
if (method.consumes != null)
{
sb.append("<li>").append("Consumes: ").append(method.consumes).append("</li>");
}
if (method.produces != null)
{
sb.append("<li>").append("Produces: ").append(method.produces).append("</li>");
}
sb.append("</ul>");
}
sb.append("</ul>");
}
return Response.ok(sb.toString()).build();
}
}
(On another note, perhaps there is something available, or I can begin work on, to model the resource listing and description that ServiceStack does so nicely: http://mono.servicestack.net/Content/Images/MetadataIndex.png)
EDIT:
See this gist for a "cleaner" example:
https://gist.github.com/wonderb0lt/10731371
Yes, it's possible. Perhaps you would like to know how? :)
Here's a "quick-n-dirty" example:
import org.jboss.resteasy.annotations.providers.jaxb.Formatted;
import org.jboss.resteasy.annotations.providers.jaxb.Wrapped;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.ResourceInvoker;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ResourceMethodRegistry;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Test;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PrintAllResourcesTest {
#Test
public void name_StateUnderTest_ExpectedBehavior() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(new MetaService());
dispatcher.getRegistry().addSingletonResource(new Service());
MockHttpResponse response = new MockHttpResponse();
MockHttpRequest request = MockHttpRequest.get("/meta")
.accept(MediaType.APPLICATION_XML);
dispatcher.invoke(request, response);
/*<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<resource method="GET">/service/</resource>
<resource method="POST">/service/</resource>
</resources>*/
String result = response.getContentAsString();
}
#XmlRootElement(name = "resource")
public static final class JaxRsResource {
#XmlAttribute String method;
#XmlValue String uri;
public JaxRsResource() {}
public JaxRsResource(String method, String uri) {
this.method = method;
this.uri = uri;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JaxRsResource that = (JaxRsResource) o;
if (method != null ? !method.equals(that.method) : that.method != null) return false;
if (uri != null ? !uri.equals(that.uri) : that.uri != null) return false;
return true;
}
#Override
public int hashCode() {
int result = method != null ? method.hashCode() : 0;
result = 31 * result + (uri != null ? uri.hashCode() : 0);
return result;
}
}
#Path("/service")
public static final class Service {
#GET
#Path("/")
public String getStuff(){
return "";
}
#POST
#Path("/")
public String postStuff(){
return "";
}
}
#Path("/meta")
public static final class MetaService {
#Context Dispatcher dispatcher;
#GET
#Path("/")
#Wrapped(element = "resources")
#Formatted
public Set<JaxRsResource> getAllResources(){
Set<JaxRsResource> resources = new HashSet<JaxRsResource>();
ResourceMethodRegistry registry = (ResourceMethodRegistry) dispatcher.getRegistry();
for (Map.Entry<String, List<ResourceInvoker>> entry : registry.getRoot().getBounded().entrySet()) {
for (ResourceInvoker invoker : entry.getValue()) {
ResourceMethod method = (ResourceMethod) invoker;
if(method.getMethod().getDeclaringClass() == getClass()){
continue;
}
for (String verb : method.getHttpMethods()) {
String uri = entry.getKey();
resources.add(new JaxRsResource(verb, uri));
}
}
}
return resources;
}
}
}
Even it is an old post, I give my answer here.
Here is the implementation from RestEasy shipped with JBoss. You can use it, or you can write your own.
The implementation returns an object with an array property where you can find a uriTemplate String for each RestEasy Resource.
You need to iterate over all entries and get the info you need:
RegistryData.entries.get(index).uriTemplate
The implementation of org.jboss.resteasy.plugins.stats.RegistryStatsResource.get method:
public RegistryData get() throws JAXBException {
ResourceMethodRegistry registry = (ResourceMethodRegistry)ResteasyProviderFactory.getContextData(Registry.class);
RegistryData data = new RegistryData();
Iterator i$ = registry.getRoot().getBounded().keySet().iterator();
label85:
while(i$.hasNext()) {
String key = (String)i$.next();
List<ResourceInvoker> invokers = (List)registry.getRoot().getBounded().get(key);
RegistryEntry entry = new RegistryEntry();
data.getEntries().add(entry);
entry.setUriTemplate(key);
Iterator i$ = invokers.iterator();
while(true) {
while(true) {
if (!i$.hasNext()) {
continue label85;
}
ResourceInvoker invoker = (ResourceInvoker)i$.next();
if (invoker instanceof ResourceMethod) {
ResourceMethod rm = (ResourceMethod)invoker;
Object method;
for(Iterator i$ = rm.getHttpMethods().iterator(); i$.hasNext(); entry.getMethods().add(method)) {
String httpMethod = (String)i$.next();
method = null;
if (httpMethod.equals("GET")) {
method = new GetResourceMethod();
} else if (httpMethod.equals("PUT")) {
method = new PutResourceMethod();
} else if (httpMethod.equals("DELETE")) {
method = new DeleteResourceMethod();
} else if (httpMethod.equals("POST")) {
method = new PostResourceMethod();
} else if (httpMethod.equals("OPTIONS")) {
method = new OptionsResourceMethod();
} else if (httpMethod.equals("TRACE")) {
method = new TraceResourceMethod();
} else if (httpMethod.equals("HEAD")) {
method = new HeadResourceMethod();
}
((ResourceMethodEntry)method).setClazz(rm.getResourceClass().getName());
((ResourceMethodEntry)method).setMethod(rm.getMethod().getName());
AtomicLong stat = (AtomicLong)rm.getStats().get(httpMethod);
if (stat != null) {
((ResourceMethodEntry)method).setInvocations(stat.longValue());
} else {
((ResourceMethodEntry)method).setInvocations(0L);
}
MediaType[] arr$;
int len$;
int i$;
MediaType mediaType;
if (rm.getProduces() != null) {
arr$ = rm.getProduces();
len$ = arr$.length;
for(i$ = 0; i$ < len$; ++i$) {
mediaType = arr$[i$];
((ResourceMethodEntry)method).getProduces().add(mediaType.toString());
}
}
if (rm.getConsumes() != null) {
arr$ = rm.getConsumes();
len$ = arr$.length;
for(i$ = 0; i$ < len$; ++i$) {
mediaType = arr$[i$];
((ResourceMethodEntry)method).getConsumes().add(mediaType.toString());
}
}
}
} else {
ResourceLocator rl = (ResourceLocator)invoker;
SubresourceLocator locator = new SubresourceLocator();
locator.setClazz(rl.getMethod().getDeclaringClass().getName());
locator.setMethod(rl.getMethod().getName());
entry.setLocator(locator);
}
}
}
}
return data;
}
See also: WildFly management - list/detect REST endpoints deployed in WildFly
In Resteasy 6.2 the above by Ondra Žižka mentioned solution caused a ClassCastException:
[2022-10-13 02:45:58,640] Artifact RegistryStatsResource:war: java.lang.Exception: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"xyz.war\".undertow-deployment" => "java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.stats.RegistryStatsResource from [Module \"deployment.xyz.war\" from Service Module Loader]
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.stats.RegistryStatsResource from [Module \"deployment.xyz.war\" from Service Module Loader]
Caused by: java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.stats.RegistryStatsResource from [Module \"deployment.xyz.war\" from Service Module Loader]"}}
I was able to resolve the issue by adding the resteasy-stats dependency:
<!-- pom.xml -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-stats</artifactId>
<version>6.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>6.2.0.Final</version>
</dependency>
For completeness, the web.xml
<web-app>
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value>
</context-param>
</web-app>
The services can be accessed via:
curl http://<AS-ip>:<AS-port>/<web context>/<rest activator>/resteasy/registry
In case anyone is still looking
hit "/resteasy/registry" on your app and it
provides XML output of all registered endpoints, associated classes/methods etc
FYI resteasy-jaxb-provider provides this functionality