JAX-RS custom pathparam validator - glassfish

I'm currently experimenting with some RESTful JAX and I want to validate a custom input. Normally regex would be fine but I need to do a more extensive check (~10 different regex patterns). I found this page when searching for jaxrs validation. I noted it says "Draft" but I thought I'd give it a try.
I wrote my parameter annotation like this:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FooBarValidator.class)
public #interface FooBarParam
{
}
The validator looks like this:
#Provider
public class FooBarValidator
implements ConstraintValidator<FooBar, Long>
{
#Override
public void initialize(FooBar constraintAnnotation)
{
}
#Override
public boolean isValid(Long value, ConstraintValidatorContext context)
{
// validation goes here, this is a test validation
return (value > 50);
}
}
The web service looks like this:
#javax.ejb.Stateless
#Path("test")
public class testRS
{
#GET
#Path("foobar/{fooBar: [0-9]+}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.TEXT_PLAIN)
public String testService(#FooBar #PathParam("fooBar") Long fooBar)
{
return "tested with: " + fooBar;
}
}
But if I call my web service with my browser using "http://localhost:8080/jaxtest/rest/test/foobar/11" the web service gets called and I'm presented with "tested with: 11". The web service works fine, except the validator doesn't get called.
I've tried setting breakpoints in the validator class and the annotation interface but none are hit.
I've got a sneaking suspicion that I'm doing something that isn't possible because of the "Draft" header in the referenced documentation. So if I'm doing something wrong or if there are alternatives, I'm glad to hear it.

Thanks to the hint #PiotrKochański gave me I've successfully implemented exactly what I wanted. The biggest problem was that I'm bound to using Glassfish. By default Glassfish uses Jersey to handle JAX stuff.
It took me well over 10 hours of struggling to complete this so let this be a time saver for anyone who stumbles upon this.
First of all, use Maven, this makes your life so much easier.
Second step, add the JBoss repo to your pom.xml
<repositories>
<repository>
<id>jboss-public-repository-group</id>
<name>JBoss Public Maven Repository Group</name>
<url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
Third step, add dependencies to pom.xml
<!-- Needed for validator interceptors -->
<dependency>
<groupId>org.jboss.seam.rest</groupId>
<artifactId>seam-rest</artifactId>
<version>3.1.0.Final</version>
</dependency>
<!-- JBoss' RS implementation -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>2.3.4.Final</version>
</dependency>
<!-- Because I use JSON I need RESTeasy be able to handle this -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jettison-provider</artifactId>
<version>2.3.4.Final</version>
</dependency>
<!-- This is THE part that integrates validation in RESTeasy -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-hibernatevalidator-provider</artifactId>
<version>2.3.4.Final</version>
</dependency>
The last dependency took me quite a while. The docs #PiotrKochański pointed to didn't mention this. However in another version of the docs I found this:
The integration between the API implementation and RESTEasy is done through the resteasy-hibernatevalidator-provider component. In order to integrate, we need to add resteasy-hibernatevalidator-provider and hibernate-validator to the classpath. With maven it's just a matter of including the following dependency:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-hibernatevalidator-provider</artifactId>
<version>2.3-RC1</version>
</dependency>
The fourth step was to add this to web.xml
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest</param-value>
</context-param>
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<servlet>
<servlet-name>REST Service</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
The fifth step was to modify the web service class like this:
#javax.ejb.Stateless
#Path("test")
public class testRS
{
#GET
#Path("foobar/{fooBar}")
#Produces(MediaType.APPLICATION_JSON)
#org.jboss.resteasy.spi.validation.ValidateRequest
public String testService(#FooBar #PathParam("fooBar") Long fooBar)
{
return "tested with: " + fooBar;
}
}
The sixth step was to modify the #interface to this:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FooBarValidator.class)
public #interface FooBarParam
{
String message() default "{constraint.FooBar}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Also as a bonus; I came across a presentation about Bean Validation by Emmanuel Bernard I thought I might share as this explains a lot of interesting stuff.

The page you found is one of the proposal on what should go into JAX-RS 2.0 (which is not final and there is no implementation of that). The plan is for JAX-RS 2.0 to integrate with Bean Validation - but as I said, that's not implemented yet.
Currently, if you want to validate input, you can declare the parameter as String (instead of Long) and do the validation as part of the resource method. Then convert to Long if the validation passes.

Related

Unable to use aspectJ interceptor in a non-spring project

I am trying to use Aspectj to execute some code after some method execution. I cannot use spring AOP as the project is a non-spring project and at this point of time I cannot change it to spring project. I have tried with a very simple implementation as below but it is not at all working:
POM of my project
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test.aspectj</groupId>
<artifactId>HelloAspectj</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.plugin.version>3.5.1</maven.compiler.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
A normal class and method after which the aspect methods will run:
package tester;
public class HelloWorld {
private String name;
public void setName(String name) {
this.name = name;
}
public void printHello() {
System.out.println("Spring 3 : Hello ! " + name);
}
}
Aspect class
package tester;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class TestAspect {
#Before(" call(void java.io.PrintStream.println(String)) " +
"&& !within(net.andrewewhite.aspects..*)")
public void beforePrintlnCall() {
System.out.println("About to make call to print Hello World");
}
#After(" call(void java.io.PrintStream.println(String)) " +
"&& !within(net.andrewewhite.aspects..*)")
public void afterPrintlnCall() {
System.out.println("Just made call to print Hello World");
}
}
Main class
package tester;
public class MainApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Hello World");
}
}
aop.xml
<aspectj>
<aspects>
<aspect name="tester.TestAspect"/>
</aspects>
</aspectj>
Project Structure:
Now i am expecting that it will price About to make call to print Hello World & Just made call to print Hello World BUt it is only printing Hello World
can someone help here..
If you want to use compile-time weaving, use
Mojohaus AspectJ Maven plugin until Java 8 and AspectJ 1.8.x or
Nick Wongdev's AspectJ Maven fork for Java 9+.
Javac via Maven Compiler plugin is not enough.
If you wish to use load-time weaving (LTW), it should be okay to compile your aspects with Javac via Maven Compiler plugin, as long as you only use annotation-driven #AspectJ syntax. For native AspectJ syntax you always need the AspectJ compiler Ajc via AspectJ Maven plugin, no matter what. For LTW you also need the weaving agent on the Java command line via -javaagent:/path/to/aspectjweaver.jar. There is also a hot-attachment option, but that is advanced stuff and you need to know what you are doing and the application must know that it wants to attach the weaver, so let's not talk about this here, as you are clearly a beginner.
All of this has been documented on the AspectJ website and the AspectJ Maven website. I have also answered numerous questions about AspectJ + Maven here, you should easily find some. Before asking questions, you should really search first. This website does not replace manuals and tutorials.

Weblogic 12.1.3 + JAX-RS 2 + jersey multipart

I have a server Weblogic 12.1.3, with JAX-RS 2.x installed as a shared library (see e.g. https://docs.oracle.com/middleware/1213/wls/RESTF/use-jersey20-ri.htm#RESTF297). This shared library includes e.g. javax.ws.rs-api-2.0.jar and jersey-media-multipart-2.5.1.jar.
Please notice I am not sure that my webapp is really using this shared library, or it is using the standard JAX-RS 1.x library.
Now I want to upload files in multipart/form-data format, so I guess I need to add this dependency on my project:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.5.1</version>
<scope>provided</scope>
</dependency>
However, the deploy fails, with error:
java.lang.ClassNotFoundException: org.glassfish.jersey.media.multipart.FormDataContentDisposition
So, I thought I could put my own library within my webapp:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.5.1</version>
</dependency>
In this second case, the deploy fails with the following error:
java.lang.ClassNotFoundException: org.glassfish.jersey.ExtendedConfig
Any idea? Thank you.
At last, I got it work. I was missing both configuration in weblogic.xml and web.xml (I didn't know it was necessary web.xml).
Weblogic.xml:
<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.9/weblogic-web-app.xsd">
<!-- Questo è per referenzialre la shared library jax-rs 2.x -->
<wls:library-ref>
<wls:library-name>jax-rs</wls:library-name>
<wls:specification-version>2</wls:specification-version>
<wls:implementation-version>2.5.1</wls:implementation-version>
<wls:exact-match>false</wls:exact-match>
</wls:library-ref>
<wls:container-descriptor>
<wls:prefer-application-packages>
<!-- apis -->
<wls:package-name>javax.ws.rs.*</wls:package-name>
<!-- guava -->
<wls:package-name>com.google.common.*</wls:package-name>
<!-- jersey1 providers -->
<wls:package-name>com.sun.jersey.*</wls:package-name>
<!-- media providers -->
<wls:package-name>org.eclipse.persistence.jaxb.rs.*</wls:package-name>
<wls:package-name>org.codehaus.jackson.jaxrs.*</wls:package-name>
<!-- wls -->
<wls:package-name>weblogic.jaxrs.api.client.*</wls:package-name>
<wls:package-name>weblogic.jaxrs.internal.api.client.*</wls:package-name>
<wls:package-name>weblogic.jaxrs.dispatch.*</wls:package-name>
<wls:package-name>weblogic.jaxrs.monitoring.util.*</wls:package-name>
</wls:prefer-application-packages>
</wls:container-descriptor>
<wls:context-root>uploader</wls:context-root>
</wls:weblogic-web-app>
Web.xml:
<servlet>
<servlet-name>JAX-RS</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>mypackage.jaxrs.JAXRSApplication
</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.filter.LoggingFilter;org.glassfish.jersey.media.multipart.MultiPartFeature
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS</servlet-name>
<url-pattern>/v1/*</url-pattern>
</servlet-mapping>
Notice in particular the reference to MultiPartFeature.
Edit
As I thought, the web.xml is not necessary. You can put all the properties inside the Application class. The configuration above is more or less equivalent to the following
#ApplicationPath("/v1")
public class JAXRSApplication extends Application {
#Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put("jersey.config.server.provider.packages", "mypackage");
properties.put("jersey.config.server.provider.classnames",
"org.glassfish.jersey.filter.LoggingFilter;org.glassfish.jersey.media.multipart.MultiPartFeature");
return properties;
}
}

No ParameterResolver registered for an #RepeatedTest

I have a not parameterized but #RepeatedTest. When running the acceptance test, the failsafe plugin throws ParameterResolutionException (no ParameterResolver registered for parameter) for #BeforeEach method (in the testcase parent class) which accepts RepetitionInfo. The pom.xml has for JUnit:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
As I've understood from the documentation (§3.11, §3.13.1), nothing else is required (RepetitionInfoParameterResolver is registered automatically and RepetitionInfo is available #BeforeEach), but it seems I'm wrong. What should I do?
... and now the code :-)
#Retention(RUNTIME)
#SuppressWarnings("javadoc")
#Target(METHOD)
#RepeatedTest(AcceptanceTestUtil.MAX_WEBBROWSER_COUNT)
#Tag("AcceptanceTest")
#Test // <- bug lies here, conflicts with #RepeatedTest
public #interface AcceptanceTest {
}

Spring Boot issues serializing java.time.LocalDateTime with Jackson to return ISO-8601 JSON timestamps?

I'm working on converting some models in a spring-boot REST API app to use java 8's java.time.LocalDateTime instead of joda's DateTime. I want the timestamps returned from API call to adhere to the ISO_8601 format. Motivation is to be forward compatible with Java 8's time (more here).
The part that's proving difficult is when it comes to serialize an object containing LocalDateTime to JSON.
For example, I have the following entity:
// ... misc imports
import java.time.LocalDateTime;
import java.time.ZoneOffset;
#Data
#Entity
#Table(name = "users")
public class User {
#Id #Column
private String id;
#Column
private String name;
#Column
private String email;
#Column
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "UTC")
private java.time.LocalDateTime createdAt;
public User(String name, String email) {
this.id = Utils.generateUUID();
this.createdAt = LocalDateTime.now(ZoneOffset.UTC);
}
}
I have also set my application.properties to turn off the dates as timestamp jackson feature:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
My maven deps:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.0.1.Final</version>
</dependency>
Finally, I try to retrieve the JSON representation via controller:
#RequestMapping("/users")
#RestController
public class UserController {
private UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#RequestMapping(
value = "/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public User getUser(#PathVariable("id") String id) {
return userService.findById(id);
}
}
When I actually make a call to this endpoint, I get the following exception:
java.lang.NoSuchMethodError:
com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase.findFormatOverrides(Lcom/fasterxml/jackson/databind/SerializerProvider;Lcom/fasterxml/jackson/databind/BeanProperty;Ljava/lang/Class;)Lcom/fasterxml/jackson/annotation/JsonFormat$Value;
Alternately I also configured the app's ObjectMapper in the configuration class:
#Configuration
public class ServiceConfiguration {
#Bean
public ObjectMapper getJacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(
com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
false
);
return objectMapper;
}
}
Any leads will be greatly appreciated.
UPDATE:
Turns out it was a version mismatch between Spring Boot's Jackson version and the one I had in my pom.xml. As Miloš and Andy proposed, once I've set the correct version and run the app with spring.jackson.serialization.write_dates_as_timestamps=true, the issue was resolved, without needing to configure the ObjectMapper or adding annotations on my LocalDateTime model fields.
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
</dependencies>
The NoSuchMethodError is because you are mixing versions of Jackson. Spring Boot 1.3.6 uses Jackson 2.6.7 and you are using 2.8.1 of jackson-datatype-jsr310.
Spring Boot provides dependency management for Jackson, including jackson-datatype-jsr310, so you should remove the version from your pom. If you want to use a different version of Jackson, you should override the jackson.version property:
<properties>
<jackson.version>2.8.1</jackson.version>
</properties>
This will ensure that all your Jackson dependencies have the same version, thereby avoiding problems with mismatched versions.
You can also, if you wish, remove your Java code that's configuring the ObjectMapper. The Java Time module will be automatically registered when it's in the classpath and writing dates as timestamps can be configured in application.properties:
spring.jackson.serialization.write-dates-as-timestamps=false
Your ObjectMapper bean must be marked as #Primary in order to be picked up by Spring. Alternatively, you can just create a JavaTimeModule bean and it will get picked up by Spring and added to the default object mapper.
You've probably seen it already but take a look at the official documentation.
The error occurs because you mix versions of Jackson. You are using version 1.3.6.RELEASE of Spring Boot. If you would migrate to Spring Boot version 2.x.x.RELEASE then you can replace the com.fasterxml.jackson.datatype dependency by a spring-boot-starter-json dependency. In this way you let Spring Boot take care of the correct Jackson version.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>

ProducerTemplate and Direct:start in camel

My camel route is :
from("direct:start")
.to("http://myhost/mypath");
I used :
ProducerTemplate template;
template.sendBody("direct:start", "This is a test message");
to send the exchange. I am getting following exception:
No consumers available on endpoint: Endpoint[direct://start].
How can i receive the same exchange in direct:start endpoint?
The reason you get this error is because you have not configured a Route that starts from direct:start.
If you have configured the Route, but did not mention it in your original query, then the next step to try is to first start the Camel Context, before calling the sendBody method.
camelContext.start();
template.sendBody("direct:start", "This is a test message");
Hope this resolves your issue.
I know this a very old question. But writing this for anyone who're still getting this kind of issue.
Scenario: During the process of a http GET method call, I am fetching some data from DB in the middle of the process and putting the data as message on to an artemis producer.
Firstly, if you're using camel with spring - you don't need to create any camel context at all. Because spring is smart enough to create camel context for you with below dependencies.
Few necessary dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>2.24.2</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jaxb-starter</artifactId>
<version>2.24.2</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jms</artifactId>
<version>2.24.2</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jackson-starter</artifactId>
<version>2.24.2</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.24.2</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-amqp</artifactId>
<version>2.24.2</version>
</dependency>
So to fix it, I created a class that extends RouteBuilder class from camel library. In this builder, I created a dummy consumer and used it to send message to an actual producer. My destination is an artemis producer endpoint.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jackson.JacksonDataFormat;
import org.apache.camel.spi.DataFormat;
import org.springframework.stereotype.Component;
#Component
public class MyRouteBuilder extends RouteBuilder {
private DataFormat marshalDataFormat;
public MyRouteBuilder(ObjectMapper objectMapper) {
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
marshalDataFormat = new JacksonDataFormat(objectMapper, MyClass.class);
}
#Override
public void configure() throws Exception {
from("direct:imaginary-consumer")
.marshal(marshalDataFormat)
.log(LoggingLevel.INFO, "Message ready to send is ${body}")
.to("producer:message-data")
.log(LoggingLevel.INFO, "Message has been sent successfully to topic.");
}
}
Below snippet is in any implementation class that carries the message body. This method takes message data and send it to the imaginary/dummy consumer we created in MyRouteBuilder class. The router class gets invoked and sends the message to the destination (producer here). It can be to http endpoints as well.
#Autowired
private ProducerTemplate producerTemplate;
public void sendMessage(Map<String, MyClass> messageBody) {
producerTemplate.sendBody("direct:imaginary-consumer", messageBody);
}
This is also posted on the Apache Camel mailing list, where its active being discussed.
http://camel.465427.n5.nabble.com/ProducerTemplate-and-direct-start-in-camel-tp5730558.html