Skip to content

Commit

Permalink
[RESTEASY-2358] Fix duplicate validation violations for EJB resources.
Browse files Browse the repository at this point in the history
  • Loading branch information
ronsigal authored and asoldano committed Nov 18, 2019
1 parent e6adbbf commit 1dc40ca
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 71 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.jboss.resteasy.plugins.validation;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
Expand All @@ -25,8 +24,6 @@ public class SimpleViolationsContainer extends org.jboss.resteasy.api.validation
{
private static final long serialVersionUID = -7895854137980651539L;

private boolean ejbsPresent;

public SimpleViolationsContainer(final Object target)
{
super(target);
Expand All @@ -45,7 +42,11 @@ public SimpleViolationsContainer(final Set<ConstraintViolation<Object>> cvs)
@Override
public void addViolations(Set<ConstraintViolation<Object>> cvs)
{
if (!ejbsPresent(cvs) || getViolations().size() == 0)
if (cvs.size() == 0)
{
return;
}
if (getViolations().size() == 0)
{
getViolations().addAll(cvs);
return;
Expand Down Expand Up @@ -95,12 +96,12 @@ static boolean compareConstraintViolation(ConstraintViolation<?> cv1, Constraint
// Can't compare leaf bean instance: one might be a proxy while the other isn't.
// Compare classes as an approximation.

if (cv1.getRootBeanClass() != null ? !compareClass(cv1.getRootBeanClass(), cv2.getRootBeanClass()) : cv2.getRootBeanClass() != null)
if (cv1.getRootBeanClass() != null ? !compareRootBeanClass(cv1, cv2) : cv2.getRootBeanClass() != null)
{
return false;
}

if (cv1.getLeafBean() != null ? !compareClass(cv1.getLeafBean().getClass(), cv2.getLeafBean().getClass()) : cv2.getLeafBean() != null)
if (cv1.getLeafBean() != null ? !compareLeafBeanClass(cv1, cv2) : cv2.getLeafBean() != null)
{
return false;
}
Expand Down Expand Up @@ -135,13 +136,32 @@ static boolean compareConstraintViolation(ConstraintViolation<?> cv1, Constraint
return true;
}

private static boolean compareClass(Class<?> c1, Class<?> c2)
private static boolean compareRootBeanClass(ConstraintViolation<?> cv1, ConstraintViolation<?> cv2)
{
while (c1.isSynthetic() && !Object.class.equals(c1))
Class<?> c1 = cv1.getRootBeanClass();
while ((c1.isSynthetic() || isEJBProxy(c1)) && !Object.class.equals(c1))
{
c1 = c1.getSuperclass();
}
while (c2.isSynthetic() && !Object.class.equals(c2))
Class<?> c2 = cv2.getRootBeanClass();
while ((c2.isSynthetic() || isEJBProxy(c2)) && !Object.class.equals(c2))
{
c2 = c2.getSuperclass();
}
return c1.equals(c2);
}

private static boolean compareLeafBeanClass(ConstraintViolation<?> cv1, ConstraintViolation<?> cv2)
{
Object o1 = cv1.getLeafBean();
Class<?> c1 = o1.getClass();
while ((c1.isSynthetic() || isEJBProxy(c1)) && !Object.class.equals(c1))
{
c1 = c1.getSuperclass();
}
Object o2 = cv2.getLeafBean();
Class<?> c2 = o2.getClass();
while ((c2.isSynthetic() || isEJBProxy(c2)) && !Object.class.equals(c2))
{
c2 = c2.getSuperclass();
}
Expand Down Expand Up @@ -284,67 +304,13 @@ private static boolean compareClassList(List<Class<?>> l1, List<Class<?>> l2)
return true;
}

private boolean ejbsPresent(Set<ConstraintViolation<Object>> set1)
{
if (ejbsPresent)
{
return true;
}
for (ConstraintViolation<Object> cv : set1)
{
if (isEjb(cv.getLeafBean().getClass()))
{
ejbsPresent = true;
return true;
}
}
return false;
}

private static boolean isEjb(Class<?> clazz)
{
while (clazz != null)
{
for (Annotation a : clazz.getAnnotations())
{
if (isEjbAnnotation(getRealClass(a.annotationType())))
{
return true;
}
}
for (Class<?> intf : clazz.getInterfaces())
{
for (Annotation a : intf.getAnnotations())
{
if (isEjbAnnotation(getRealClass(a.annotationType())))
{
return true;
}
}
}
clazz = clazz.getSuperclass();
}
return false;
}

private static boolean isEjbAnnotation(Class<?> c)
{
if ("javax.ejb.Stateless".equals(c.getName()) ||
"javax.ejb.Stateful".equals(c.getName()) |
"javax.ejb.Singleton".equals(c.getName()))
{
return true;
}
return false;

}

private static <T> Class<?> getRealClass(Class<?> clazz)
{
while (clazz.isSynthetic())
{
clazz = clazz.getSuperclass();
}
return clazz;
/**
* Determine whether an object is indeed a valid EJB proxy object created by this API.
*
* @param object the object to test
* @return {@code true} if it is an EJB proxy, {@code false} otherwise
*/
private static boolean isEJBProxy(final Class<?> clazz) {
return clazz.getName().contains("$$$view");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.jboss.resteasy.test.cdi.ejb;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.hibernate.validator.HibernateValidatorPermission;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.resteasy.api.validation.ViolationReport;
import org.jboss.resteasy.test.cdi.ejb.resource.EJBCDIValidationEJBProxyGreeterResource;
import org.jboss.resteasy.test.cdi.ejb.resource.EJBCDIValidationEJBProxyGreeting;
import org.jboss.resteasy.utils.PermissionUtil;
import org.jboss.resteasy.utils.PortProviderUtil;
import org.jboss.resteasy.utils.TestUtil;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* @tpSubChapter CDI
* @tpChapter Integration tests
* @tpTestCaseDetails EJB, CDI, Validation, and RESTEasy integration test: RESTEASY-2358
* @tpSince RESTEasy 3.10.0
*/
@RunWith(Arquillian.class)
@RunAsClient
public class EJBCDIValidationEJBProxyTest {

private static final String VALID_REQUEST = "{\n"
+ "\"name\":\"Hugo\"\n"
+ "}";

private static final String INVALID_REQUEST = "{\n"
+ "\"name\":\"123456789010\"\n"
+ "}";

private static Client client;

@Deployment
public static Archive<?> createTestArchive() {
WebArchive war = TestUtil.prepareArchive(EJBCDIValidationEJBProxyTest.class.getSimpleName());
war.addClass(EJBCDIValidationEJBProxyGreeting.class);
war.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
war.setWebXML(EJBCDIValidationEJBProxyTest.class.getPackage(), "web.xml");
war.addAsManifestResource(PermissionUtil.createPermissionsXmlAsset(
new HibernateValidatorPermission("accessPrivateMembers")
), "permissions.xml");
return TestUtil.finishContainerPrepare(war, null,
EJBCDIValidationEJBProxyGreeterResource.class);
}

private String generateURL(String path) {
return PortProviderUtil.generateURL(path, EJBCDIValidationEJBProxyTest.class.getSimpleName());
}

@BeforeClass
public static void init() {
client = ClientBuilder.newClient();
}

@AfterClass
public static void close() {
client.close();
}

@Test
public void buggyBeanValidation() {
final WebTarget greeterTarget = client.target(generateURL("/greeter"));

Response response = greeterTarget.request().post(Entity.entity(INVALID_REQUEST, MediaType.APPLICATION_JSON));
String answer = response.readEntity(String.class);
ViolationReport r = new ViolationReport(answer);
TestUtil.countViolations(r, 0, 0, 0, 1, 0);

response = greeterTarget.request().post(Entity.entity(VALID_REQUEST, MediaType.APPLICATION_JSON));
final String helloHugo = response.readEntity(String.class);
Assert.assertEquals("Hello Hugo!", helloHugo);

response = greeterTarget.request().post(Entity.entity(INVALID_REQUEST, MediaType.APPLICATION_JSON));
answer = response.readEntity(String.class);
r = new ViolationReport(answer);
TestUtil.countViolations(r, 0, 0, 0, 1, 0);

response = greeterTarget.request().post(Entity.entity(INVALID_REQUEST, MediaType.APPLICATION_JSON));
answer = response.readEntity(String.class);
r = new ViolationReport(answer);
TestUtil.countViolations(r, 0, 0, 0, 1, 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.jboss.resteasy.test.cdi.ejb.resource;

import javax.ejb.Remote;
import javax.ejb.Singleton;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

@Singleton
@Remote
@Path("greeter")
public class EJBCDIValidationEJBProxyGreeterResource {

@POST
@Consumes(MediaType.APPLICATION_JSON)
public String greet(@Valid EJBCDIValidationEJBProxyGreeting greeting) {
return "Hello " + greeting.getName() + "!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.jboss.resteasy.test.cdi.ejb.resource;

import javax.validation.constraints.Size;

public class EJBCDIValidationEJBProxyGreeting {

@Size(min = 0, max = 10, message = "value should be between 0 and 10")
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<web-app>

<display-name>Archetype Created Web Application</display-name>

<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

<context-param>
<param-name>resteasy.jndi.resources</param-name>
<param-value>
java:global/EJBCDIValidationEJBProxyTest/EJBCDIValidationEJBProxyGreeterResource
</param-value>
</context-param>

</web-app>

0 comments on commit 1dc40ca

Please sign in to comment.