Custom Annotation for Cascading [Mongo DB]

Lets do some crazy thing. Sprin+Mongo does not come with some bells and balls and does not have any functionality to do operations like cascading entities. In my project, I had a requirement.

I have an One To One relationship from TbPostVO to TbObjectVO. Every post is associated with an image.

Here goes the VOs.

com.anupam.vo.TbPostVO

 
package com.anupam.vo;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import com.anupam.annotation.MyCascade;

@Document(collection = "posts")
public class TbPostVO implements Serializable
{

    private static final long serialVersionUID = 1L;
    @Id
    private Integer postID;

    @Field
    private String scrap;

    @Field
    private Date date;

    @DBRef
    private TbUserVO userVO;

    @DBRef
    //@MyCascade
    private TbObjectVO objVO;

    public Integer getPostID()
    {
	return postID;
    }

    public void setPostID(Integer postID)
    {
	this.postID = postID;
    }

    public String getScrap()
    {
	return scrap;
    }

    public void setScrap(String scrap)
    {
	this.scrap = scrap;
    }

    public Date getDate()
    {
	return date;
    }

    public void setDate(Date date)
    {
	this.date = date;
    }

    public TbUserVO getUserVO()
    {
	return userVO;
    }

    public void setUserVO(TbUserVO userVO)
    {
	this.userVO = userVO;
    }

	public TbObjectVO getObjVO() {
		return objVO;
	}

	public void setObjVO(TbObjectVO objVO) {
		this.objVO = objVO;
	}

   

}

com.anupam.vo.TbObjectVO

package com.anupam.vo;

import java.io.Serializable;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document(collection = "objects")
public class TbObjectVO implements Serializable
{

    private static final long serialVersionUID = 1L;

    @Id
    private Integer objectID;

    @Field
    private String type;

    @Field
    private byte[] objectData;

    public Integer getObjectID()
    {
	return objectID;
    }

    public void setObjectID(Integer objectID)
    {
	this.objectID = objectID;
    }

    public String getType()
    {
	return type;
    }

    public void setType(String type)
    {
	this.type = type;
    }

    public byte[] getObjectData()
    {
	return objectData;
    }

    public void setObjectData(byte[] objectData)
    {
	this.objectData = objectData;
    }

}

Now lets look at the code to save a post along with image object,

com.anupam.service.PostSRVImpl

package com.anupam.service;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.anupam.dao.CommonDAO;
import com.anupam.dao.PostDAO;
import com.anupam.dao.UserDAO;
import com.anupam.vo.TbObjectVO;
import com.anupam.vo.TbPostVO;

@Service
public class PostSRVImpl implements PostSRV
{
    @Autowired
    private CommonDAO cd;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private PostDAO postDAO;

    @Override
    public void savePost(String scrap, String imgPath) throws Exception
    {
	TbPostVO postVO = new TbPostVO();
	postVO.setPostID(cd.getNextID("posts").intValue());
	postVO.setDate(new Date());
	postVO.setScrap(scrap);
	postVO.setUserVO(userDAO.getUserByID(3));

	TbObjectVO objVO = new TbObjectVO();
	objVO.setObjectID(cd.getNextID("objects").intValue());
	objVO.setType("jpg");
	objVO.setObjectData(Files.readAllBytes(Paths.get(imgPath)));
	postVO.setObjVO(objVO);
	

	postDAO.savePost(postVO);

    }

    @Override
    public List<TbPostVO> getAllPosts() throws Exception
    {
	return postDAO.getAllPosts();
    }

}

If you call the savePost() method and want to see the necessary results in the database, you can see that it is not associated with an image. Because MongoDB does not do anything cascading. You have to do it manually.

And in my case, I have found a solution somewhere in internet to create an custom annotation. Here I have created a custom annotation @MyCascade. 

com.anupam.annotation.MyCascade

package com.anupam.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface MyCascade
{

}

And now an event listener class com.anupam.annotation.MyCascadeHandler

package com.anupam.annotation;

import java.lang.reflect.Field;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;


public class MyCascadeHandler extends AbstractMongoEventListener<Object>
{
    @Autowired
    private MongoTemplate template;
    
    @Override
    public void onBeforeConvert(final Object source)
    {
	ReflectionUtils.doWithFields(source.getClass(), new FieldCallback()
	{

	    @Override
	    public void doWith(
		    Field field) throws IllegalArgumentException, IllegalAccessException
	    {
		ReflectionUtils.makeAccessible(field);
		if (field.isAnnotationPresent(DBRef.class) && field.isAnnotationPresent(MyCascade.class))
		{
		    final Object fieldValue = field.get(source);
		    template.save(fieldValue);
		}
		else
		{
		    System.out.println(field.getName());
		}
	    }
	});
    }
}

Now, configure this event listener in spring configuration,
com.anupam.config.SpringConfig

package com.anupam.config;

import java.net.UnknownHostException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;

import com.anupam.annotation.MyCascadeHandler;
import com.mongodb.MongoClient;

@Configuration
public class SpringConfig
{

    @Bean
    public MongoDbFactory dbFactory() throws UnknownHostException
    {
	MongoClient mc = new MongoClient("localhost", 9999);
	return new SimpleMongoDbFactory(mc, "mydb");
    }

    @Bean
    public MongoTemplate mongoTemplate() throws UnknownHostException
    {
	MongoTemplate template = new MongoTemplate(dbFactory());
	return template;
    }
    
    @Bean
    public MyCascadeHandler myCascade()
    {
	return new MyCascadeHandler();
    }
}

Now, call this @MyCascade in the relationship

    @DBRef
    @MyCascade
    private TbObjectVO objVO;

That’s it.We will work on it later.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s