I am trying to set up a simple RESTful web service on Netbeans. Basicly, I have a container where the components are simple order objects with 3 attributes : id, total, and list of items.
The GET and the POST on the container work fine as well as the GET on the components. What is not working is the PUT and the DELETE on the components: I don't receive any error, just nothing happens.
Since the GET is working, probably the error is on the client side, so I am posting the client class in charge of the single orders here.
package restclientjson;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget;
public class JSONOrderClient {
private WebTarget webTarget;
private Client client;
private static final String BASE_URI = "http://localhost:8080/RESTServerJSON/webresources";
public JSONOrderClient(String id) {
client = javax.ws.rs.client.ClientBuilder.newClient();
String resourcePath = java.text.MessageFormat.format("orders/{0}", new Object[]{id});
webTarget = client.target(BASE_URI).path(resourcePath);
}
public void setResourcePath(String id) {
String resourcePath = java.text.MessageFormat.format("orders/{0}", new Object[]{id});
webTarget = client.target(BASE_URI).path(resourcePath);
}
public void putJson(Object requestEntity) throws ClientErrorException {
webTarget.request(javax.ws.rs.core.MediaType.TEXT_PLAIN).put(javax.ws.rs.client.Entity.entity(requestEntity, javax.ws.rs.core.MediaType.APPLICATION_JSON));
}
public void delete() throws ClientErrorException {
webTarget.request().delete();
}
public <T> T getJson(Class<T> responseType) throws ClientErrorException {
WebTarget resource = webTarget;
return resource.request(javax.ws.rs.core.MediaType.APPLICATION_JSON).get(responseType);
}
public void close() {
client.close();
}
}
Edit: Here is the Server side code:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ServiceCore;
import dto.Order;
import java.util.Map;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.DELETE;
import javax.ws.rs.core.MediaType;
/**
* REST Web Service OrderResource
*
*
*/
public class OrderResource {
private String id;
private Map<String,Order> orderMap;
/**
* Creates a new instance of OrderResource
*/
private OrderResource(String id,Map<String,Order> orderMap) {
this.id = id;
this.orderMap = orderMap;
}
/**
* Get instance of the OrderResource
*/
public static OrderResource getInstance(String id,Map<String,Order> orderMap) {
// The user may use some kind of persistence mechanism
// to store and restore instances of OrderResource class.
return new OrderResource(id,orderMap);
}
/**
* Retrieves representation of an instance of ServiceCore.OrderResource
* #return an instance of dto.Order
*/
#GET
#Produces(MediaType.APPLICATION_JSON)
public Order getJson() {
return orderMap.get(id);
}
/**
* PUT method for updating or creating an instance of OrderResource
* #param content representation for the resource
*/
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public void putJson(Order content) {
orderMap.put(id, content);
}
/**
* DELETE method for resource OrderResource
*/
#DELETE
public void delete() {
orderMap.remove(id);
}
}
package ServiceCore;
import dto.Order;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* REST Web Service
*
*/
#Path("/orders")
public class OrdersResource {
#Context
private UriInfo context;
private static Map<String, Order> orderMap= new HashMap<String,Order>();
{
orderMap.put("1", new Order(1,2,new String[]{"water","coffee"}));
orderMap.put("2", new Order(3,400,new String[]{"milk"}));
}
private static int id = 3;
/**
* Creates a new instance of OrdersResource
*/
public OrdersResource() {
}
/**
* Retrieves representation of an instance of ServiceCore.OrdersResource
* #return an instance of dto.Order[]
*/
#GET
#Produces(MediaType.APPLICATION_JSON)
public dto.Order[] getJson() {
Order[] orders = new Order[orderMap.size()];
for(int i=0; i<orderMap.size(); i++){
orders[i]=orderMap.get((i+1)+"");
}
return orders;
}
/**
* POST method for creating an instance of OrderResource
* #param content representation for the new resource
* #return an HTTP response with content of the created resource
*/
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response postJson(Order content) {
orderMap.put(id+"", content);
id++;
return Response.created(context.getAbsolutePath()).build();
}
/**
* Sub-resource locator method for {id}
*/
#Path("{id}")
public OrderResource getOrderResource(#PathParam("id") String id) {
return OrderResource.getInstance(id,orderMap);
}
}
In Java, most web frameworks use more than one Servlet to handle requests, often discarding the servlet as soon as the request is handled.
You probably need to put your Map of items into a different context, like the Application context, which is persistent across the entire life of the Application.
Related
Hi I have mongoDb database which contains separate db for different shops, but the collections inside all the dbs have same structure, when I get the request from post service I want to insert the data to the respective database based in the id in the request. Please advice how to do this in springboot java or Kotlin
AMAZON
- ProductDetails
FLIPKART
- ProductDetails
EBAY
- ProductDetails
Now I have a single database and insert all product details in a single database and I want to add different databases for different shops
spring.data.mongodb.host=mongo
spring.data.mongodb.port=27017
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.username=admin
spring.data.mongodb.password=pass
spring.data.mongodb.database=admin
Since you are new to Spring boot and MongoDB, I am providing you the detailed steps as follows to connect multiple mongo DB in single application. This is one of the most simple ways to configure and connect multiple mongo DB. Hopefully, it will be helpful (don't forget to vote up if it is :-)) -
1) Package Structure -
2) Create an abstract MongoDB Config class -
package com.akash.mongo.multidb.config;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
/**
* Abstract class for configuring different MongoTemplate for different DB
* #author Akash
*
*/
public abstract class AbstractMongoDbConfig {
private String host;
private String username;
private String password;
private String database;
private int port;
public void setHost(String host) {
this.host = host;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setDatabase(String database) {
this.database = database;
}
public void setPort(int port) {
this.port = port;
}
public MongoDbFactory mongoDbFactory() {
MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
MongoClient mongoClient = new MongoClient(new ServerAddress(host, port), mongoCredential, new MongoClientOptions.Builder().build());
return new SimpleMongoDbFactory(mongoClient, database);
}
public abstract MongoTemplate getMongoTemplate() throws Exception;
}
3) Extend the abstract class to create configuration for each DB
AmazonDbConfig
package com.akash.mongo.multidb.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Configuration class for Amazon DB
* #author Akash
*
*/
#Configuration
#ConfigurationProperties(prefix="amazon.mongodb")
#EnableMongoRepositories(basePackages= {"com.akash.mongo.multidb.repository.amazon"}, mongoTemplateRef="amazonMongoTemplate")
public class AmazonDbConfig extends AbstractMongoDbConfig {
private static final Logger logger = LoggerFactory.getLogger(AmazonDbConfig.class);
#Override
#Bean(name="amazonMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
logger.info("Creating MongoTemplate for Amazon DB");
return new MongoTemplate(mongoDbFactory());
}
}
EbayDbConfig
package com.akash.mongo.multidb.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Configuration class for ebay DB
* #author Akash
*
*/
#Configuration
#ConfigurationProperties(prefix="ebay.mongodb")
#EnableMongoRepositories(basePackages= {"com.akash.mongo.multidb.repository.ebay"}, mongoTemplateRef="ebayMongoTemplate")
public class EbayDbConfig extends AbstractMongoDbConfig {
private static final Logger logger = LoggerFactory.getLogger(EbayDbConfig.class);
#Override
#Bean(name="ebayMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
logger.info("Creating MongoTemplate for Ebay DB");
return new MongoTemplate(mongoDbFactory());
}
}
FlipkartDbConfig
package com.akash.mongo.multidb.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Configuration class for Flipkart DB
* #author Akash
*
*/
#Configuration
#ConfigurationProperties(prefix="flipkart.mongodb")
#EnableMongoRepositories(basePackages= {"com.akash.mongo.multidb.repository.flipkart"}, mongoTemplateRef="flipkartMongoTemplate")
public class FlipkartDbConfig extends AbstractMongoDbConfig {
private static final Logger logger = LoggerFactory.getLogger(FlipkartDbConfig.class);
#Override
#Primary
#Bean(name="flipkartMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
logger.info("Creating MongoTemplate for Flipkart DB");
return new MongoTemplate(mongoDbFactory());
}
}
Notice that each of these configuration class is creating its own MongoTemplate and it is enabling its own MongoRepository. Also one of these needs to be #Primary otherwise the spring boot will throw error. It doesn't matter which of these is primary; ultimately these will be connecting to their own repository
4) Create entities and a repository for each DB.
You need to create a repository for each DB now. Given that your collection is same for all the DBs, I have created following sample entity -
package com.akash.mongo.multidb.entity;
import java.io.Serializable;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* Sample Entity class
* #author Akash
*
*/
#Document(collection="productDetails")
public class ProductDetails implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private ObjectId id;
#Field("productName")
private String productName;
#Field("productDesc")
private String productDesc;
#Field("productQuantity")
private String productQuantity;
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getProductDesc() {
return productDesc;
}
public void setProductDesc(String productDesc) {
this.productDesc = productDesc;
}
public String getProductQuantity() {
return productQuantity;
}
public void setProductQuantity(String productQuantity) {
this.productQuantity = productQuantity;
}
}
You can create/modify the entity class as per your collection details.
AmazonRepository
package com.akash.mongo.multidb.repository.amazon;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
*
* #author Akash
*
*/
#Repository
public interface AmazonRepository extends MongoRepository<ProductDetails, ObjectId> {
}
FlipkartRepository
package com.akash.mongo.multidb.repository.flipkart;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.akash.mongo.multidb.entity.ProductDetails;
#Repository
public interface FlipkartRepository extends MongoRepository<ProductDetails, ObjectId> {
}
EbayRepository
package com.akash.mongo.multidb.repository.ebay;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
*
* #author Akash
*
*/
#Repository
public interface EbayRepository extends MongoRepository<ProductDetails, ObjectId> {
}
Again, each repository needs to be its own package otherwise there will be errors while running the application. This is the one disadvantage of this solution where you have to create as many repository packages as no of DBs you want to connect.
5) Service implementation and connecting to different repositories
ProductDetailsService Interface
package com.akash.mongo.multidb.service;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
* Sample interface with one add method
* #author Akash
*
*/
public interface ProductDetailsService {
/**
*
* #param productOrigin - the shop name i.e. Amazon, Flipkart or ebay.
* #param productDetails - the product details to add
*/
public void addProductDetails(String productOrigin, ProductDetails productDetails) throws RuntimeException;
}
ProductDetailsServiceImpl Class -
package com.akash.mongo.multidb.service;
import java.util.Map;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
* Implementation of ProductDetailsService interface
* #author Akash
*
*/
public class ProductDetailsServiceImpl implements ProductDetailsService {
private static final Logger logger = LoggerFactory.getLogger(ProductDetailsServiceImpl.class);
/*
* Spring boot will autowire all the repositories along with their name
* amazonRepository - amazon repository instance
* ebayRepository - ebay repository instance and so on
*/
#Autowired
Map<String, MongoRepository<ProductDetails, ObjectId>> repositories;
#Override
public void addProductDetails(String productOrigin, ProductDetails productDetails) throws RuntimeException {
logger.info("Adding product details into {} db", productOrigin);
//if productOrigin is Amazon; repositoryName will be amazonRepository which is already present in spring boot
String repositoryName = productOrigin.toLowerCase()+"Repository";
if(repositories.containsKey(repositoryName)) {
repositories.get(repositoryName).save(productDetails);
} else {
logger.error("{} shop is undefined in DB. Check and try again", productOrigin);
throw new RuntimeException("Shop doesnot exist in MongoDb");
}
}
}
ProductOrigin you can derive from your request or headers whatever information is available to you.
6) Lastly, application.properties
Change the database, username and password details for each DB. Try not to use Admin credentials; Instead create username & password for each DB separately and update application.properties.
#MongoDb connection properties for Flipkart DB
flipkart.mongodb.database=flipkart
flipkart.mongodb.host=http://127.0.0.1
flipkart.mongodb.port=27017
flipkart.mongodb.username=flipkart
flipkart.mongodb.password=flipkart
#MongoDb connection properties for Amazon DB
amazon.mongodb.database=amazon
amazon.mongodb.host=http://127.0.0.1
amazon.mongodb.port=27017
amazon.mongodb.username=amazon
amazon.mongodb.password=amazon
#MongoDb connection properties for ebay DB
ebay.mongodb.database=ebay
ebay.mongodb.host=http://127.0.0.1
ebay.mongodb.port=27017
ebay.mongodb.username=ebay
ebay.mongodb.password=ebay
Now, if you need to add any new database, you just need to add one config class similar to AmazonDbConfig, one more package with the required repositories for that DB and connection details in application.properties. No change is required in service till your collection is same for all the DBs.
If you have multiple collections, you can add entity and repository for each collection (group all the respositories for single shop in one package) and solution should still hold good.
I'm trying to create a simple error handling project, that will give JSON with error data after receiving an error (for example 404, 422 or 500). I work with code from this site, but it's not working for me.
I actually have this two classes:
BasicController class
package com.mycompany.jsonerrorhandler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
/**
* Class to catch all exception
*/
public class BasicController
{
#ExceptionHandler (Exception.class)
#ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleAllExceptions(Exception ex)
{
return new JsonError(ex.getMessage()).asModelAndView();
}
}
JsonError class
package com.mycompany.jsonerrorhandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;
import com.google.common.collect.ImmutableMap;
/**
* Class that defines what JSON Error looks like
*/
public class JsonError
{
private final String message;
public JsonError(String message)
{
this.message = message;
}
public ModelAndView asModelAndView()
{
MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
return new ModelAndView(jsonView, ImmutableMap.of("error", message));
}
}
I wonder what I need to connect them and receive JSON (or maybe there is other solution for this problem).
Based on the like you provided, the JsonError class should contain the following:
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;
import com.google.common.collect.ImmutableMap;
public class JsonError
{
private final String message;
public JsonError(String message) {
this.message = message;
}
public ModelAndView asModelAndView() {
MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
return new ModelAndView(jsonView, ImmutableMap.of("error", message));
}
}
I make a REST webservice with SpringBoot web and the Morphia DAO (to use MongoDB).
As I did when I used Hibernate on MySQL, I would like to use Generic entities, repositories and endpoints so that I just have to set my entities, inherit Repositories and Services and let use the generated CRUD with REST calls.
It almost done, but I encounter a problem with the generic update of my entities with Morphia. Everything I've seen so far talks about manually setting a request with the fields that have to change ; but in the Hibernate way, we just setted the Id field, called persist() and it automatically knowed what changed and applied changes in database.
Here is some code.
BaseEntity.java
package org.beep.server.entity;
import org.mongodb.morphia.annotations.Entity;
#Entity
abstract public class BaseEntity {
public static class JsonViewContext {
public interface Summary {}
public interface Detailed extends Summary{}
}
protected String id;
public void setId(String id) {
this.id = id;
}
}
User.java (one of my final entities)
package org.beep.server.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.*;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.mongodb.morphia.annotations.*;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.ws.rs.FormParam;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
#Entity
#Indexes(
#Index(value="identifier", fields=#Field("email"))
)
#Builder
#NoArgsConstructor
#AllArgsConstructor
final public class User extends BaseEntity {
/**
* User's id
*/
#Id
#JsonView(JsonViewContext.Summary.class)
private String id;
/**
* User's email address
*/
#Getter #Setter
#JsonView(JsonViewContext.Summary.class)
#FormParam("email")
#Indexed
#Email
private String email;
/**
* User's hashed password
*/
#Getter
#JsonView(JsonViewContext.Detailed.class)
#FormParam("password")
#NotEmpty
private String password;
/**
* Sets the password after having hashed it
* #param clearPassword The clear password
*/
public void setPassword(String clearPassword) throws NoSuchAlgorithmException, InvalidKeySpecException {
PasswordEncoder encoder = new BCryptPasswordEncoder();
String hashedPassword = encoder.encode(clearPassword);
setHashedPassword(hashedPassword);
}
/**
* Directly sets the hashed password, whithout hashing it
* #param hashedPassword The hashed password
*/
protected void setHashedPassword(String hashedPassword) {
this.password = hashedPassword;
}
/**
* Converts the user to a UserDetail spring instance
*/
public UserDetails toUserDetails() {
return new org.springframework.security.core.userdetails.User(
getEmail(),
getPassword(),
true,
true,
true,
true,
AuthorityUtils.createAuthorityList("USER")
);
}
}
EntityRepository.java (my base repository, inheriting from the Morphia one)
package org.beep.server.repository;
import org.beep.server.entity.BaseEntity;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.dao.BasicDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
public class EntityRepository<Entity extends BaseEntity> extends BasicDAO<Entity, ObjectId> {
#Autowired
protected EntityRepository(Datastore ds) {
super(ds);
}
}
UserRepository.java (my user repository)
package org.beep.server.repository;
import org.beep.server.entity.User;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.dao.BasicDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
#Repository
public class UserRepository extends EntityRepository<User> {
#Autowired
protected UserRepository(Datastore ds) {
super(ds);
}
}
EntityService.java (the generic service, used from the Rest endpoints)
package org.beep.server.service;
import org.beep.server.entity.BaseEntity;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.repository.EntityRepository;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.query.Query;
import org.mongodb.morphia.query.UpdateOperations;
import java.util.List;
public abstract class EntityService<Entity extends BaseEntity, Repository extends EntityRepository> implements ServiceInterface<Entity> {
protected Repository repository;
public EntityService(Repository repository) {
this.repository = repository;
}
/**
* {#inheritDoc}
*/
public Entity create(Entity entity) throws UserEmailAlreadyExistsException {
repository.save(entity);
return entity;
}
/**
* {#inheritDoc}
*/
public void delete(String id) throws EntityNotFoundException {
//repository.deleteById(id).;
}
/**
* {#inheritDoc}
*/
public List<Entity> findAll() {
return repository.find().asList();
}
/**
* {#inheritDoc}
*/
public Entity findOneById(String id) throws EntityNotFoundException {
return (Entity) repository.get(id);
}
/**
* {#inheritDoc}
*/
public Entity update(String id, Entity entity) {
// Try to get the old entity, and to set the Id on the inputed one
// But how can I merge the two ? If I persist like that, I will just have the modified fields, others
// will be set to null...
Entity oldEntity = (Entity) repository.get(id);
entity.setId(id);
repository.save(entity);
// Create update operations works, but I have to set the changing fields manually...
// not so compatible with generics !
/*final Query<Entity> updateSelection = repository.createQuery().filter("_id",id);
repository.createUpdateOperations().
repository.update(updateSelection,entity);*/
return entity;
}
}
UserService.java
package org.beep.server.service;
import org.beep.server.entity.Message;
import org.beep.server.entity.User;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.repository.UserRepository;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Key;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.ws.rs.BadRequestException;
import java.util.List;
import java.util.Optional;
#Service
public class UserService extends EntityService<User, UserRepository> {
#Autowired
public UserService(UserRepository repository) {
super(repository);
}
}
RestResource.java (my base Rest Endpoint)
package org.beep.server.api.rest.v1;
import com.fasterxml.jackson.annotation.JsonView;
import org.beep.server.entity.BaseEntity;
import org.beep.server.entity.User;
import org.beep.server.entity.BaseEntity;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.service.EntityService;
import org.beep.server.service.ServiceInterface;
import org.beep.server.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
public class RestResource<Entity extends BaseEntity, Service extends EntityService> {
protected Service service;
// Default constructor private to avoid blank constructor
protected RestResource() {
this.service = null;
}
/**
* Creates an object
* #param object Object to create
* #return The newly created object
*/
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
Entity create(#RequestBody #Valid Entity object) throws UserEmailAlreadyExistsException {
return service.create(object);
}
/**
* Deletes an object from its id
* #param id Object to delete id
* #return The deleted object
* #throws EntityNotFoundException
*/
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
User delete(#PathVariable("id") String id) throws EntityNotFoundException {
service.delete(id);
return new User();
}
/**
* Gets all the objects
* #return All the objects
*/
#RequestMapping(method = RequestMethod.GET)
#JsonView(BaseEntity.JsonViewContext.Summary.class)
List<Entity> findAll() {
return service.findAll();
}
/**
* Finds one object from its id
* #param id The object to find id
* #return The corresponding object
* #throws EntityNotFoundException
*/
#RequestMapping(value = "{id}", method = RequestMethod.GET)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
Entity findById(#PathVariable("id") String id) throws EntityNotFoundException {
return service.findOneById(id);
}
/**
* Updates an object
* #param object The object to update
* #return The updated object
*/
#RequestMapping(value = "{id}", method = RequestMethod.PUT)
#JsonView(BaseEntity.JsonViewContext.Detailed.class)
Entity update(#PathVariable String id, #RequestBody #Valid Entity object) {
return service.update(id, object);
}
/**
* Handles the EntityNotFound exception to return a pretty 404 error
* #param ex The concerned exception
*/
#ExceptionHandler
#ResponseStatus(HttpStatus.NOT_FOUND)
public void handleEntityNotFound(EntityNotFoundException ex) {
}
/**
* Handles the REST input validation exceptions to return a pretty 400 bad request error
* with more info
* #param ex The validation exception
* #return A pretty list of the errors in the form
*/
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
public List<ObjectError> handleValidationFailed(MethodArgumentNotValidException ex) {
// TODO : Check and improve the return of this method according to the front
// The concept is to automatically bind the error dans the failed parameter
return ex.getBindingResult().getAllErrors();
}
}
You've run into one of the difficult questions with Morphia. Based on the code you posted above you should look into the merge method here.
The important thing to remember is that this is not a deep merge, only top level fields, if you have complicated data objects this probably won't help.
It essentially works like this:
T Entity -> Map and then takes the map and runs a recursive update over the non-null fields like so: update({_id:#Id-field},{$set:mapOfEntityFields})
The standard transformation rules from T Entity apply ->
Map, like it does for save.
For a deep merging of any generic entity it would require you to handle this yourself with a custom method.
This is a good example from another question on SO dealing with deep merging using JSON partials in Spring over complex entities using the org.codehaus.jackson.map.ObjectMapper. It should be easily adapted to your problem.
If neither of these help you, please comment in my answer and we can work out a custom recursive method that should work for you. Hope that helps.
I am trying to create a simple web service which outputs using json, but am not getting the desired Json output.
POJO:
package com.rest.resource;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Track implements Serializable
{
#XmlElement
String singer = "ABC";
#XmlElement
String title = "XYZ";
}
Service:
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import com.rest.resource.Track;
#Path("/json/metallica")
public class JSONService
{
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Track createTrackInJSON(final Track track)
{
return track;
}
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final Track track = new Track();
return Response.status(201).entity(track).build();
}
}
On /get I get
{"singer":"ABC","title":"XYZ"}
but I want "track": {"singer":"ABC","title":"XYZ"}
I am unable yo print the root element.
I tried using a CustomJAXBContextResolver class but did not work for me? Can anyone give an example of the same?
If you want to use the ContextResolver, you'd need to use the JSONConfiguration and switch the JSON Notation. You could do that by adding a class like this:
#Provider
public class MyJAXBContextProvider implements ContextResolver<JAXBContext> {
private JSONJAXBContext trackCtx;
public MyJAXBContextProvider() throws JAXBException {
trackCtx = new JSONJAXBContext(JSONConfiguration.mappedJettison().build(), Track.class);
}
public JAXBContext getContext(Class<?> type) {
if(type == Track.class) {
return trackCtx;
}
return null;
}
}
Adding that class produced this for me:
{"track":{"singer":"ABC","title":"XYZ"}}
For more info check out the Jersey Docs
You'd have to wrap Track with another object:
public class TrackWrapper {
Track track;
TrackWrapper(Track track) {
this.track=track;
}
}
and return an instance of TrackWrapper,
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final TrackWrapper trackWrapper = new TrackWrapper(new Track());
return Response.status(201).entity(trackWrapper).build();
}
}
and just in case, if you're gonna use JSON only you don't need the JAXB annotations.
I did try going through the following links
How to wire in a collaborator into a Jersey resource?
and
Access external objects in Jersey Resource class
But still i am unable to find a working sample which shows how to inject into a Resource class.
I am not using Spring or a web container.
My Resource is
package resource;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/something")
public class Resource
{
#MyResource
Integer foo = null;
private static String response = "SampleData from Resource";
public Resource()
{
System.out.println("...constructor called :" + foo);
}
#Path("/that")
#GET
#Produces("text/plain")
public String sendResponse()
{
return response + "\n";
}
}
My Provider is
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Integer>
{
#Override
public ComponentScope getScope()
{
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Integer arg2)
{
return new Injectable<Object>()
{
#Override
public Object getValue()
{
return new Integer(99);
}
};
}
}
My EndpointPublisher is
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
class EndpointPublisher
{
public static void main(final String[] args)
{
final String address = "http://localhost:8080/";
final Map<String, String> config = new HashMap<String, String>();
config.put("com.sun.jersey.config.property.packages", "resource");
try
{
GrizzlyWebContainerFactory.create(address, config);
System.out.println("server started ....." + address);
callGet();
}
catch (final Exception e)
{
e.printStackTrace();
}
}
public static void callGet()
{
Client client = null;
ClientResponse response = null;
client = Client.create();
final WebResource resource =
client.resource("http://localhost:8080/something");
response = resource.path("that")
.accept(MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_XML_TYPE)
.type(MediaType.TEXT_XML)
.get(ClientResponse.class);
System.out.println(">>>> " + response.getResponseDate());
}
}
My annotation being
#Retention(RetentionPolicy.RUNTIME)
public #interface MyResource
{}
But when i execute my EndpointPublisher i am unable to inject foo!!
Your InjectableProvider is not implemented correctly. The second type parameter should not be the type of the field you are trying to inject - instead it should be the context - either java.lang.reflect.Type class or com.sun.jersey.api.model.Parameter class. In your case, you would use Type. So, your InjectableProvider implementation should look as follows:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import java.lang.reflect.Type;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Type> {
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Type arg2) {
if (Integer.class.equals(arg2)) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
} else {
return null;
}
}
}
There is a helper class for per-request injectable providers (PerRequestTypeInjectableProvider) as well as singleton injectable providers (SingletonTypeInjectableProvider), so you can further simplify it by inheriting from that:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
#Provider
public class MyResourceProvider extends PerRequestTypeInjectableProvider<MyResource, Integer> {
public MyResourceProvider() {
super(Integer.class);
}
#Override
public Injectable<Integer> getInjectable(ComponentContext ic, MyResource a) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
}
}
Note that for these helper classes the second type parameter is the type of the field.
And one more thing - the injection happens after the constructor is called, so the constructor of your resource will still print out ...constructor called :null, but if you change your resource method to return foo, you'll see the response you'll get will be 99.
This solution works well and I wanted to share what I found to enable CDI on jersey resources.
Here is the simplest bean ever :
package fr.test;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
#RequestScoped
public class Test {
private int i;
#PostConstruct
public void create() {
i = 6;
}
public int getI() {
return i;
}
}
In your resource class, we just inject this bean, as we would do in a any normal context :
package fr.test;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/login")
public class LoginApi {
#Inject
private Test test;
#GET
#Produces("text/plain")
public String getIt() {
return "Hi there!" + test;
}
}
And here is the key. We define a Jersey "InjectionProvider" which will be responsible of beans' resolution :
package fr.test;
import javax.inject.Inject;
import java.lang.reflect.Type;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import fr.xxxxxxxxxx.ApplicationBeans;
#Provider
public class InjectionProvider implements InjectableProvider<Inject, Type> {
public ComponentScope getScope() {
// CDI will handle scopes for us
return ComponentScope.Singleton;
}
#Override
public Injectable<?> getInjectable(ComponentContext context,
Inject injectAnno, Type t) {
if (!(t instanceof Class))
throw new RuntimeException("not injecting a class type ?");
Class<?> clazz = (Class<?>) t;
final Object instance = ApplicationBeans.get(clazz);
return new Injectable<Object>() {
public Object getValue() {
return instance;
}
};
}
}
InjectableProvider is typed with the kind of annotation we are handling, and the context type (here, normal java type)
ApplicationBeans is just a simple helper for bean resolution. Here is its content :
package fr.xxxxxxxxxx;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import fr.xxxxxxxxxxxxx.UnexpectedException;
/**
* Gives direct access to managed beans - Designed to be used from unmanaged code
*
* #author lgrignon
*
*/
#ApplicationScoped
public class ApplicationBeans
{
protected static ApplicationBeans instance;
#Inject
private BeanManager beanManager;
/**
* Gets instance
*
* #return Instance from managed environment
*/
public static ApplicationBeans instance()
{
if (instance == null)
{
BeanManager beanManager;
InitialContext ctx = null;
try
{
ctx = new InitialContext();
beanManager = (BeanManager)ctx.lookup("java:comp/BeanManager");
}catch(NamingException e)
{
try
{
beanManager = (BeanManager)ctx.lookup("java:app/BeanManager");
}catch(NamingException ne)
{
throw new UnexpectedException("Unable to obtain BeanManager.", ne);
}
}
instance = getBeanFromManager(beanManager, ApplicationBeans.class);
}
return instance;
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public static <T> T get(final Class<T> beanType, Annotation... annotations)
{
return instance().getBean(beanType, annotations);
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public <T> T getBean(final Class<T> beanType, Annotation... annotations)
{
return getBeanFromManager(beanManager, beanType, annotations);
}
#SuppressWarnings("unchecked")
private static <T> T getBeanFromManager(BeanManager beanManager, final Class<T> beanType, Annotation... annotations)
{
Set<Bean<?>> beans = beanManager.getBeans(beanType, annotations);
if (beans.size() > 1)
{
throw new UnexpectedException("Many bean declarations found for type %s (%s)", beanType.getSimpleName(), beansToString(beans));
}
if (beans.isEmpty())
{
throw new UnexpectedException("No bean declaration found for type %s", beanType.getSimpleName());
}
final Bean<T> bean = (Bean<T>)beans.iterator().next();
final CreationalContext<T> context = beanManager.createCreationalContext(bean);
return (T)beanManager.getReference(bean, beanType, context);
}
private static String beansToString(Collection<Bean<?>> beans)
{
String[] beansLabels = new String[beans.size()];
int i = 0;
for (final Bean<?> bean : beans)
{
beansLabels[i++] = bean.getName();
}
return Arrays.toString(beansLabels);
}
}
Hope this will help those who want to enable CDI injection in their Jersey resources.
Bye !