Spring: Generic Resource Controller - java

I want to create a generic controller that would like something like this:
#RequestMapping("/api/v1/resource/{resource}")
public class StandardController {
private StandardResourceService standardResourceService;
public StandardController(StandardResourceService standardResourceService) {
this.standardResourceService = standardResourceService;
}
#GetMapping("/{id}")
public Object getResource(
#PathVariable(value="resource") String resource,
#PathVariable(value="id") Long id,
HttpServletRequest request){
return standardResourceService.getEntity(id, resource);
}
#PostMapping
#Transactional
Object newResource(#PathVariable(value="resource") String resource, #RequestBody Object newObject) {
standardResourceService.postResource(resource, newObject);
return newObject;
}
}
The idea is that I don't want to have a lot of endpoints/services/controllers for each entity I'll define in the project. I want to create a standard one for each entity and if I need some custom behaviour for some entities, I'll just extend the base service/controller.
The service I've made looks like this:
#Service
public class StandardResourceService {
#Autowired
private EntityManager entityManager;
#Autowired
private ModelMapper modelMapper;
public <T, ID> T findById(Class<T> type, ID id) {
return entityManager.find(type, id);
}
#Transactional
public void saveObject(Object object) {
entityManager.persist(object);
}
public Object getEntity(long resourceId, String resource) throws EntityNotFoundException {
Object resourceOpt;
Object dto;
try{
Class<?> cls = Class.forName("com.base.package.models." + resource);
resourceOpt = findById(cls, resourceId);
}catch (ClassNotFoundException ex){
throw new EntityNotFoundException("Resource " + resource + " could not be found with id: " + resourceId);
}
if(resourceOpt == null) {
throw new EntityNotFoundException("Resource " + resource + " could not be found with id: " + resourceId);
}
try {
Class<?> dtoClass = Class.forName("com.base.package.dto." + resource + "DTO");
dto = modelMapper.map(resourceOpt, dtoClass);
} catch (ClassNotFoundException ex) {
return resourceOpt;
}
return dto;
}
public Object postResource(String resource, Object newObject) throws RuntimeException {
try{
Class<?> clazz = Class.forName("com.base.package.models." + resource);
//saveObject(instance);
}catch (ClassNotFoundException ex){
throw new EntityNotFoundException("");
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return newObject;
}
}
The GET is working pretty good. I return a DTO if any, otherwise I return the entity itself.
By the way when using the POST I don't know how to instantiate the correct class with the body I'm sending in the POST.
Is there a way to do this?
Am I approaching this problem correctly?

Related

How to delete a document from the cache based on a delete event happening in MongoDB?

I am trying to set up an update cache mechanism for whenever a change happens in MongoDB. For this I have implemented a listener as so:
#Bean
public MessageListenerContainer messageListenerContainer(MongoTemplate template){
MessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer(template);
messageListenerContainer.start();
ChangeStreamRequest<BinDataItem> request = ChangeStreamRequest.builder()
.collection("BIN")
.publishTo(getBinListener())
.build();
messageListenerContainer.register(request, BinDataItem.class);
return messageListenerContainer;
}
#Bean
#Qualifier("BinDataUpdateListener")
public BinDataUpdateListener<ChangeStreamDocument<Document>, BinDataItem> getBinListener(){
return new BinDataUpdateListener<ChangeStreamDocument<Document>, BinDataItem>();
}
And the update the method is:
public class BinDataUpdateListener<C, T> implements MessageListener<ChangeStreamDocument<Document>, BinDataItem> {
#Autowired
#Qualifier("binDataLoader")
CacheManager cacheManager;
#Override
public void onMessage(Message<ChangeStreamDocument<Document>, BinDataItem> message) {
BinDataItem binDataItem = (BinDataItem)message.getBody(); // this value comes as null for a delete operation
String operationType = message.getRaw().getOperationType().getValue();
try {
cacheManager.updateCache(operationType, binDataItem, message.getRaw().getDocumentKey());
}
catch(RedisCommandTimeoutException ex){
addToSLogger(DomainConstants.GET_BIN_DATA_STRING, DomainConstants.ERR_CODE_MS1003, ex);
throw ex;
}
catch(RedisBusyException ex){
addToSLogger(DomainConstants.GET_BIN_DATA_STRING, DomainConstants.ERR_CODE_MS1002, ex);
throw ex;
}
catch(RedisConnectionFailureException ex){
addToSLogger(DomainConstants.GET_BIN_DATA_STRING, DomainConstants.ERR_CODE_MS1002, ex);
throw ex;
}
catch(Exception ex){
addToSLogger(DomainConstants.GET_BIN_DATA_STRING, DomainConstants.ERR_CODE_MS1001, ex);
throw ex;
}
}
private void addToSLogger(String messageName, String errorCode, Exception e){
Slogger.get()
.withTag(DomainConstants.MSG_ERROR)
.withField(DomainConstants.MSG_NAME, messageName)
.withField(DomainConstants.ERR_CODE, errorCode)
.withThrowable(e)
.info(e.getMessage());
}
}
The BinDataItem pojo is:
#Document(collection = "BIN")
#JsonIgnoreProperties(ignoreUnknown=true)
public class BinDataItem implements Serializable {
#BsonProperty(value = "_id")
private String id;
private String bin;
}
The problem is that whenever an event of type delete happens, the object deleted comes as null in the program. I need the field bin of the deleted object in mongo to delete it in the redis cache. I know how to get the objectId but all the other fields I don't.

Java reflection error Java.lang.NoSuchMethodException, but method exists

I can't get to work java reflection in Spring boot with Controller and JdbcTemplate.
Default controller looks like:
public class DefaultController {
private static final Logger logger = LoggerFactory.getLogger(DefaultController.class);
public JsonResponseDataModel selectOneAuto(Long id, Class<?> repository, HttpServletResponse response){
final JsonResponseDataModel result = new JsonResponseDataModel();
System.out.println("The name of class is " + repository.getName());
Method[] methods = repository.getMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
try {
//Method method = repository.getClass().getMethod("selectOne", Long.class);
Method method = repository.getClass().getDeclaredMethod("selectOne", Long.class);
method.invoke(repository, id);
logger.info("selectOneAuto : id={} ", id);
} catch (EmptyResultDataAccessException e) {
result.setEmptyResultDataAccessException("id", id.toString());
} catch (DataAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
}
Inside CompanyRepository class is defined selectOne method with Long input:
#Transactional(readOnly=true)
public CompanyModel selectOne(Long id) {
CompanyModel result = null;
final String sql = "SELECT * FROM company WHERE id=?";
return jdbcTemplate.queryForObject(sql, new Object[]{id}, new CompanyRowMapper());
}
When I create a new class "CompanyController extends DefaultController" and call method selectOneAuto:
selectOneAuto(id, new CompanyRepository().getClass(), response);
Then it ends with error on line "Method method = repository.getClass().getDeclaredMethod("selectOne", Long.class);"
"Java.lang.NoSuchMethodException: java.lang.Class.selectOne(java.lang.Long)"
But the for loop inside "selectOneAuto" method outputs method named "selectOne". What is wrong here?
Your code attempts to call the method on an instance of Class.
method.invoke(repository, id);
repository object is an instance of Class since you are passing new CompanyRepository().getClass() as parameter.
The second point to be noted is repository is an instance of Class already so there is no need to call getClass() on this object.
You should obtain the method object using the following code :
Method method = repository.getDeclaredMethod("selectOne", Long.class);
And then this should work :
CompanyRepository repsitoryObj = new CompanyRepository();
method.invoke(repsitoryObj, id);
Or a better and cleaner way is to simply change the type of your repository parameter as CompanyRepository your method will look as follows:
public JsonResponseDataModel selectOneAuto(Long id, CompanyRepository repository, HttpServletResponse response){
final JsonResponseDataModel result = new JsonResponseDataModel();
System.out.println("The name of class is " + repository.getClass().getName());
Method[] methods = repository.getClass().getMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
try {
//Method method = repository.getClass().getMethod("selectOne", Long.class);
Method method = repository.getClass().getDeclaredMethod("selectOne", Long.class);
method.invoke(repository, id);
logger.info("selectOneAuto : id={} ", id);
} catch (EmptyResultDataAccessException e) {
result.setEmptyResultDataAccessException("id", id.toString());
} catch (DataAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return result;
}

spring does not reginase "redirect:/path" returning a simple string

The redirect just returns a simple string. It does not recognise the redirect.
The response just print "redirect:/getUser/"
Can you help me?
thanks already.
the return should be the list of user with the new user added
#Slf4j
#RestController
public class UserController {
#Autowired
UserService userService;
#GetMapping(value = "/getUser", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public List<UserDTO> getUsers() throws Exception {
List<UserDTO> oListUsers = new ArrayList<UserDTO>();
try {
oListUsers = userService.findAll();
} catch (Exception ex) {
log.error("Error getUsers. Cause: " + ex.getMessage());
throw new Exception("Data can not be retrieved. Cause: " + ex.getMessage());
}
return oListUsers;
}
#PostMapping("/createUser")
public String createUser(#Valid #RequestBody UserDTO newUser) throws Exception {
try {
userService.save(newUser);
} catch (Exception ex) {
log.error("Error createUser. Cause: " + ex.getMessage());
throw new Exception("Data can not be save. Cause: " + ex.getMessage());
}
return "redirect:/getUser/";
}
}
I would suggest below changes .
#PostMapping("/user")
public ResponseEntity createUser(#Valid #RequestBody UserDTO newUser) throws Exception {
UserDTO createdUser;
try {
createdUser=userService.save(newUser);
} catch (Exception ex) {
log.error("Error createUser. Cause: " + ex.getMessage());
throw new Exception("Data can not be save. Cause: " + ex.getMessage());
}
return new ResponseEntity(createdUser);
}
and provide other API to list all the users

Common Argument Pass in Method

I have a method called makePersistent in my DAO class.
Currntly we have this method in all dao classes and what i need to do is convert this method to common format. So is there any way to do it?
Method in UserDao Class
public void makePersistent(User model) throws InfrastructureException {
try {
getSession().saveOrUpdate(model);
getSession().flush();
getSession().clear();
} catch (org.hibernate.StaleObjectStateException ex) {
throw new InfrastructureException(Labels.getString("com.tran.msg.objectDeletedOrUpdated"));
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
Method in HolidayDao Class
public void makePersistent(Holiday model) throws InfrastructureException {
try {
getSession().saveOrUpdate(model);
getSession().flush();
getSession().clear();
} catch (org.hibernate.StaleObjectStateException ex) {
throw new InfrastructureException(Labels.getString("com.tran.msg.objectDeletedOrUpdated"));
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
Please help me to get rid of this redundant coding.
Thank you.
Just use Object the hibernate will persist it.
public void makePersistent(Object model) throws InfrastructureException {
try {
getSession().saveOrUpdate(model);
getSession().flush();
getSession().clear();
} catch (org.hibernate.StaleObjectStateException ex) {
throw new InfrastructureException(Labels.getString("com.tran.msg.objectDeletedOrUpdaed"));
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
Create a superclass for your DAOs with a type parameter and make your DAO classes extend that superclass with the appropriate type argument. For example:
public class BaseDao<T> {
public void makePersistent(T model) throws InfrastructureException {
try {
getSession().saveOrUpdate(model);
getSession().flush();
getSession().clear();
} catch (org.hibernate.StaleObjectStateException ex) {
throw new InfrastructureException(Labels.getString("com.tran.msg.objectDeletedOrUpdated"));
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
}
public class UserDao extends BaseDao<User> {
// ...
}
public class HolidayDao extends BaseDao<Holiday> {
// ...
}
UserDao and HolidayDao inherit the makePersistent method from BaseDao, so you don't have to implement it again in every DAO class.

BeanToPropertyValueTransformer and commons-collections4

In commons-collections 3.2.1 the following one-liner worked nicely to retrieve the myProperty values of the objects inside myCollection:
Collection<MyType> myTypes = (Collection<MyType>) CollectionUtils.collect(myCollection, new BeanToPropertyValueTransformer("myProperty"))
Only drawback is that generics are not supported, so type casting needs to be done.
What would be a solution that works in commons-collection4, taking advantage of generics?
Apparently they removed BeanToPropertyValueTransformer from the release of apache-commons-collection4.
I managed to achieve the same behavior by defininig a custom Transformer. The introduction of generics eliminates the necessity of casting the output collection:
Collection<MyInputType> myCollection = ...
Collection<MyType> myTypes = CollectionUtils.collect(myCollection, new Transformer<MyInputType, MyType>() {
#Override
public MyType transform(MyInputType input) {
return input.getMyProperty();
}
}
You could also write your own Transformer that uses reflection
class ReflectionTransformer<O>
implements
Transformer<Object, O> {
private String reflectionString;
public ReflectionTransformer(String reflectionString) {
this.reflectionString = reflectionString;
}
#SuppressWarnings("unchecked")
#Override
public O transform(
Object input) {
try {
return (O) BeanUtils.getProperty(input, reflectionString);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
and use it same as you used to do with BeanToPropertyValueTransformer
Collection<MyType> myTypes = CollectionUtils.collect(myCollection, new ReflectionTransformer<MyType>("myProperty"));
Try using the below class (using commons-collection4) instead -
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.lang.reflect.InvocationTargetException;
public class BeanToPropertyValueTransformer implements Transformer {
private final Log log = LogFactory.getLog(this.getClass());
private String propertyName;
private boolean ignoreNull;
public BeanToPropertyValueTransformer(final String propertyName) {
this(propertyName, false);
}
public BeanToPropertyValueTransformer(final String propertyName, final boolean ignoreNull) {
super();
if ((propertyName != null) && (propertyName.length() > 0)) {
this.propertyName = propertyName;
this.ignoreNull = ignoreNull;
} else {
throw new IllegalArgumentException(
"propertyName cannot be null or empty");
}
}
public Object transform(final Object object) {
Object propertyValue = null;
try {
propertyValue = PropertyUtils.getProperty(object, propertyName);
} catch (final IllegalArgumentException e) {
final String errorMsg = "Problem during transformation. Null value encountered in property path...";
if (ignoreNull) {
log.warn("WARNING: " + errorMsg + e);
} else {
final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
if (!BeanUtils.initCause(iae, e)) {
log.error(errorMsg, e);
}
throw iae;
}
} catch (final IllegalAccessException e) {
final String errorMsg = "Unable to access the property provided.";
final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
if (!BeanUtils.initCause(iae, e)) {
log.error(errorMsg, e);
}
throw iae;
} catch (final InvocationTargetException e) {
final String errorMsg = "Exception occurred in property's getter";
final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
if (!BeanUtils.initCause(iae, e)) {
log.error(errorMsg, e);
}
throw iae;
} catch (final NoSuchMethodException e) {
final String errorMsg = "No property found for name [" +
propertyName + "]";
final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
if (!BeanUtils.initCause(iae, e)) {
log.error(errorMsg, e);
}
throw iae;
}
return propertyValue;
}
public String getPropertyName() {
return propertyName;
}
public boolean isIgnoreNull() {
return ignoreNull;
}
}

Categories

Resources