I've got a JavaEE application with a simple REST service where I want to support the content types application/json and application/xml. Everything works as expected when the client sends an Accept header with one of these content types in it.
When this header is missing though, I want to answer with application/json as default, but no matter what I've tried so far, application/xml will be used.
Configuration Class
#ApplicationPath("api")
public class ApplicationConfig extends Application {
}
Service Class
#Path("users")
public class UserResource {
#Path("/{username}")
#GET
// The following variants didn't change the behaviour:
// #Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
// #Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML + ";q=0.5"})
// #Produces({MediaType.APPLICATION_JSON + ";q=1.0", MediaType.APPLICATION_XML + ";q=0.5"})
// #Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML + ";qs=0.5"})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public User userDetails(#PathParam("username") final String username) {
return new User();
}
}
(Generated) User Class
The class I'm returning is generated (maven-jaxb2-plugin) with JAXB annotations to support XML without further configuration:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "User", propOrder = {
"username"
})
#XmlRootElement(name = "user")
public class User
implements Serializable, Equals, HashCode
{
private final static long serialVersionUID = 1701L;
#XmlElement(required = true)
protected String username;
// ...
}
Further Information
JavaEE 7
Java 8
Same behaviour with Glassfish 4.1 and Wildfly 9.0.1
Related
I have a controller that accepts path parameter called 'jobName'. The #ValidateJobName is the custom validator that validates the user input. If the input is wrong then it throws the error below
"Invalid Job name, valid job names are: vendor, service, product,
pricing, currency, contract"
The issue I am facing is that, when I am testing my rest controller API the test case always fails by returning the above error even when the job name is one of the acceptable values but when I remove #ValidateJobName custom annotation from the controller my test cases gets passed.
The #ValidateJobName and Controller works all good when triggered from Postman client but when I do unit testing the test case fails.
I have tried lot of blogs and googled but could not get a solution, Below are my Controller and JUnit testcase.
Please help!
JobController.java
#Validated
#Slf4j
#RestController
public class JobController {
#Autowired
ReportService reportService;
#Autowired
ReportConfig reportConfig;
#RequestMapping(value = "/importjob/{jobName}", method = RequestMethod.GET)
ResponseEntity<DataIntegrationResponse> getReport(#PathVariable #ValidateJobName String jobName) throws Exception {
log.info("Received a request to launch the " + jobName + " Job");
return reportService.getReport(jobName);
}
}
JobControllerTest.java
#ExtendWith(MockitoExtension.class)
#WebMvcTest(JobController.class)
#AutoConfigureMockMvc
public class JobControllerTest {
#MockBean
ReportService reportService;
#MockBean
ReportConfig rep;
#MockBean
JobMapping jmap;
#Autowired
public MockMvc mockMvc;
#Test
public void testGetReport() throws Exception {
String jobNameInput="vendor";
HttpStatus httpStatus = HttpStatus.OK;
String fitsReportName = "idex_fits_vendor.csv";
String jobName = "WFitsVendorJob";
String jobStatus = "STARTED";
Long jobInstanceId = 1022L;
String message = "WFitsVendorJob triggered successfully.";
DataIntegrationResponse response = new DataIntegrationResponse(LocalDateTime.now(), httpStatus, fitsReportName, jobName, jobStatus, jobInstanceId, message);
ResponseEntity<DataIntegrationResponse> responseEntity = new ResponseEntity<DataIntegrationResponse>(response, HttpStatus.OK);
Mockito.when(reportService.getReport(jobNameInput)).thenReturn(responseEntity);
mockMvc.perform(get("/importjob/{jobName}", "vendor")).andExpect(status().isOk());
}
JobNameValidator.java
#Component
public class JobNameValidator implements ConstraintValidator<ValidateJobName, String>{
#Autowired
private JobMapping jobMap;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value!=null && !jobMap.getMappings().containsKey(value)) { return false; }
return true;
}
}
ValidateJobName.java - interface
#Documented
#Constraint(validatedBy = JobNameValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
public #interface ValidateJobName {
String message() default "Invalid Job name, valid job names are: vendor, service, product, pricing, currency, contract";
Class<?>[] groups() default {};
Class<? extends Payload> [] payload() default {};
}
This is because you use a mock of JobMapping
#MockBean
JobMapping jmap;
Your JobNameValidator receive a Mock and doesn't know what to return when calling the containsKey method.
First solution is to tell what to do with this mock :
Mockito.when(jobMapping.getMappings()).thenReturn(// Map containing "vendor");
The second solution is to import your real JobMapping class instead of a mock:
#ExtendWith(MockitoExtension.class)
#WebMvcTest(JobController.class)
#AutoConfigureMockMvc
#Import(JobMapping.class)
class JobControllerTest {
// #MockBean
// JobMapping jmap;
}
I have simple class and field id is annotated with custom deserializer.
public class TestRequest implements Serializable {
#NotNull
#Pattern(regexp = "^[a-zA-Z0-9=+]*$")
#JsonDeserialize(using = StringDeserializer.class)
#JsonProperty
private String id;
//getter
//setter
}
and the custom deserializer class :
public class StringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
Iterator<String> iterator = jsonParser.readValuesAs(String.class);
String id = iterator.next();
return id + "0000";
}
}
When i test with with Jersey REST test, the deserilizer is called and works as expected. But with websphere it does not called. Any idea why its not called.
I am using jaxrs 1.1
You may need to add the #Provider annotation to your StringDeserializer class, otherwise the JAX-RS runtime will not recognize the class as a provdier.
Even though your class extends JsonDeserializer<String>, which itself is probably annotated with #Provider, the JAX-RS runtime will not scan libraries for annotations, as doing negatively impacts performance.
I made it working in Websphere by moving the annotation #JsonDeserialize(using = StringDeserializer.class) to setter method of the class
public class TestRequest implements Serializable {
#NotNull
#Pattern(regexp = "^[a-zA-Z0-9=+]*$")
#JsonProperty
private String id;
#JsonDeserialize(using = StringDeserializer.class)
public setId(String id){
this.id=id;
}
//getter
//setter
}
I have some problem finding out how get JAX-RS 2.0 to work with CDI on wildfly 10. I got some answer on another post that was a mix of JAX-RS 1.0/2.0 and they used other dependencies than the included libraries in Wildfly.
My objective is to inject a singleton enterprise bean that encapsulate business logic that resides in the same jar into my REST resource. The REST resource class is supposed to be request scoped and only deal with REST functionality (request and response). My POJO classes are JAXB notated.
How can I use JAX-RS 2.0 with the include CDI libraries in Wildfly 10?
The bean interface
#Local
public interface DateBean {
Date getLocalFormatDate();
}
The bean
#Singleton
public class DateBeanImpl implements DateBeanLocal {
private static final Logger LOG = Logger.getLogger("org.test.logger");
public DateBean() {
LOG.fine("DateBean");
}
#Override
public Date getLocalFormatDate() {
Calendar cal = Calendar.getInstance();
TimeZone localZone = TimeZone.getDefault();
cal.setTimeZone(localZone);
Date localTime = cal.getTime();
return localTime;
}
}
The REST resource
#Path("classroom")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ClassRoomResource {
private static final Logger LOG = Logger.getLogger("org.clearbyte.logger");
#Inject private DateBean dateBean;
public ClassRoomResource() {
LOG.fine("ClassRoomResource");
}
#GET
#Path("{id}/getDummy")
public ClassRoom getDummy(#PathParam("id") long id) {
ClassRoom room = new ClassRoom();
room.setRoomName("Dummy");
room.setRoomNr(id);
return room;
}
#GET
#Path("localDate")
#Produces({MediaType.TEXT_HTML})
public Response getLocalformatDate() {
LOG.fine("DateBean variable: " +dateBean);
Date localDate = dateBean.getLocalDate();
LOG.fine("Local date: " +localDate);
return Response.status(Response.Status.OK)
.entity(localDate.toString())
.build();
}
}
The Resteasy implementation of JAX-RS 2.0 are included in Wildlfy 10, so there is no need to add further dependencies.
The interface doesn’t need #Localwhen is resides in the same jar/war for CDI to find it. To make the enterprise bean singleton in CDI use the #ApplicationScope, you can omit the #Singleton notation if you don't need a managed bean with read/write synchronisation.
#ApplicationScoped
public class DateBeanImpl implements DateBean {
private static final Logger LOG = Logger.getLogger("org.test.logger");
public DateBean() {
LOG.fine("DateBean");
}
#Override
public Date getLocalFormatDate() {
//DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
TimeZone localZone = TimeZone.getDefault();
cal.setTimeZone(localZone);
Date localTime = cal.getTime();
return localTime;
}
}
The make the REST resource request scoped use the #RequestScoped notation. Notice that the #Inject inject the interface and not the implementation of the bean.
#RequestScoped
#Path("classroom")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ClassRoomResource {
private static final Logger LOG = Logger.getLogger("org.clearbyte.logger");
#Inject private DateBean dateBean;
...
No configuration of the web.xml is necessary if you a extend the jax-rs Application class.
#ApplicationPath("rest")
public class ClassRoomApp extends Application {
private final Set<Class<?>> resources = new HashSet<>();
public ClassRoomApp() {
resources.add(ClassRoomResource.class);
}
#Override
public Set<Class<?>> getClasses() {
return resources;
}
}
I'm new to Jersey 2 and JAX-RS, so probably I'm missing something.
What I'm trying to do is a test program to define a coding style in rest services developing.
The test was written in JAVA and uses JERSEY 2.22.2, JDK 1.8.31, MOXY AS JSON Provider.
I defined a Resource with GET methods to support LIST/DETAIL. Due to the size of my POJO, I used some filters and everything was fine.
// 1) First of all I defined the annotation.
#Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#EntityFiltering
public #interface MyDetailView {
public static class Factory extends AnnotationLiteral<MyDetailView>
implements MyDetailView {
private Factory() {
}
public static MyDetailView get() {
return new Factory();
}
}
// 2) Once defined the annotation, I used to
// programmaticaly exclude the list of subItems in the response...
#XmlRootElement
public class MyPojo {
...
//*** THIS SHOULD BE FILTERED IF THE ANNOTATION IS NOT SPECIFIED IN THE RESPONSE ***
#MyDetailView
private List<SubItem> subItems = new ArrayList<SubItem>();
public List<SubItem> getSubItems() {
return subItems;
}
public void setSubItems(List<SubItem> subItems) {
this.subItems = subItems;
}
}
// 3) I registered the EntityFilteringFeature
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
....
register(EntityFilteringFeature.class);
}
// 4) Finally, I wrote the code to include/exclude the subItems
/*
The Resource class has getCollection() and getItem() methods...
getCollection() adds the annotation only if filterStyle="detail"
getItem() always add the annotation
*/
#Path(....)
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class MyResource extends SecuredResource {
//filterStyle -> "detail" means MyDetailAnnotation
#GET
public Response getCollection(
#QueryParam("filterStyle") String filterStyle,
#Context UriInfo uriInfo) {
//THIS CODE AFFECTS THE RESPONSE
boolean detailedResponse = "detail".equals(filterStyle);
Annotation[] responseAnnotations = detailedResponse
? new Annotation[0]
: new Annotation[]{MyDetailView.Factory.get()};
//pojo collection...
MyPagedCollection myCollection = new MyPagedCollection();
//.....
ResponseBuilder builder = Response.ok();
return builder.entity(myCollection, responseAnnotations).build();
}
#GET
#Path("/{id}")
public Response getItem(#PathParam("{id}") String idS, #Context UriInfo uriInfo) {
MyPOJO pojo = ...
Annotation[] responseAnnotations = new Annotation[]{MyDetailView.Factory.get()};
return Response.ok().entity(pojo, responseAnnotations).build();
}
}
After the first test, I tried to use the SelectableEntityFilteringFeature to allow the client to ask for specific fields in the detail, so I changed the ApplicationConfig
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
....
register(EntityFilteringFeature.class);
register(SelectableEntityFilteringFeature.class);
property(SelectableEntityFilteringFeature.QUERY_PARAM_NAME, "fields");
}
and I've add the "fields" QueryParam to the Resource getItem() method...
#GET
#Path("/{id}")
public Response getDetail(#PathParam({id}) String id,
#QueryParam("fields") String fields,
#Context UriInfo uriInfo) {
....
But as long as I registered the SelectableEntityFilteringFeature class, the EntityFilteringFeature class stopped working. I tried to add "fields" parameter to one of the Resource methods, it worked perfectly. But the MyDetailAnnotation was completely useless.
I tried to register it using a DynamicFeature
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if ("MyResource".equals(resourceInfo.getResourceClass().getSimpleName())
&& "getItem".equals(resourceInfo.getResourceMethod().getName())) {
//*** IS THE CORRECT WAY TO BIND A FEATURE TO A METHOD? ***
//
context.register(SelectableEntityFilteringFeature.class);
context.property(SelectableEntityFilteringFeature.QUERY_PARAM_NAME, "fields");
}
}
Now the questions:
1) Why registering both the SelectableEntityFilteringFeature feature breaks the EntityFilteringFeature?
2) What is the correct way to bind a feature to a method with the DynamicFeature interface?
Thanks in advance.
This is my first post to Stack Overflow, I hope it was written complaining the rules.
Short answer: you can't. It appears to be a bug as of 2.25.1 and up to 2.26(that I tested with). https://github.com/jersey/jersey/issues/3523
SelectableEntityFilteringFeature implictily registers EntityFilteringFeature (As mentioned here). So I don't see a need to add this.
Since you need Annotation based filtering, you can exclude registering SelectableEntityFilteringFeature.
You can just do,
// Set entity-filtering scope via configuration.
.property(EntityFilteringFeature.ENTITY_FILTERING_SCOPE, new Annotation[] {MyDetailView.Factory.get()})
// Register the EntityFilteringFeature.
.register(EntityFilteringFeature.class)
// Further configuration of ResourceConfig.
You can refer to this example for usage and this example for registering the filter.
So you can remove SelectableEntityFilteringFeature and try just the above mentioned way to register it.
In my app I'm using MOXy JAXB with JAX-RS (Jersey) on Glassfish server,
I have the following REST webservice:
#Named
#RequestScoped
#Path("/product")
public class ProductService extends BaseServiceFacade<Product, Integer, ProductVO> {
#EJB(mappedName="java:global/myAppEAR/myAppEJB/ProductServiceRest")
ProductServiceRestRemote productServiceRestRemote;
// ...
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/featuredlists")
public List<List<ProductVO>> featuredlists() {
return productServiceRestRemote.featuredlists();
}
}
When I try to test the REST service accessing:
localhost:8080/atlanteusPortal/rest/product/featuredlists
I get:
java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
at org.eclipse.persistence.jaxb.rs.MOXyJsonProvider.getDomainClass(MOXyJsonProvider.java:267)
If I put a debug breakpoint before the method return I can see that the List<List<ProductVO>> chunkList is populated but it's not transformed into JSON
Can someone point out a solution to send a List<List<Object>> type via JSON using JAX-RS MOXy and Jersey?
I solved the issue using a workaround encapsulating List of Lists inside an object
called ProductListVO:
#XmlRootElement
public class ProductListVO extends BaseVO<String> {
private List<ProductVO> productVOs;
public List<ProductVO> getProductVOs() {
return productVOs;
}
public void setProductVOs(List<ProductVO> productVOs) {
this.productVOs = productVOs;
}
public static ProductListVO buildVO(List<Product> t) {
ProductListVO vo = new ProductListVO();
List<ProductVO> prodVOs = new ArrayList<ProductVO>();
StringBuilder sb = new StringBuilder();
for (Product product : t) {
sb.append(product.getId()).append('-');
prodVOs.add(ProductVO.buildVO(product));
}
vo.setId(sb.substring(0, sb.length() - 1));
vo.setProductVOs(prodVOs);
return vo;
}
}
in Service method:
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/featured")
public List<ProductListVO> featuredlists() {
return productServiceRestRemote.featuredLists();
}