In almost every application we require dynamic constants which can be loaded dynamically or on start up. Drop downs are one of the good example. System parameters from your table is another example. Let's take a specific scenario,
Say suppose we have got three security roles:
1) USER_ROLE,
2) USER_ADMIN,
3) USER_API
The requirement is we have to refer them in the form of constants in our code as well as some times we may have to compose drop down let's say on account settings page we have to show that drop down, a drop down for roles. For a drop down we may require key (code) and label for each constant.
We have to implement these reference values in the database so we can change these reference values with out changing code (or without a deployment of code). Also we still would be able to refer them in code but the key and value would be coming from database.
Initially, I wanted to implement it as Enum but I could not. If someone can, I would happy to see implementation.
In this implementation I have introduced annotation (@ReferenceList) to map the reference type to database.
My reference types will have some common operations so we have got CommonReferenceTypeImplementation as follows:
Say suppose we have got three security roles:
1) USER_ROLE,
2) USER_ADMIN,
3) USER_API
The requirement is we have to refer them in the form of constants in our code as well as some times we may have to compose drop down let's say on account settings page we have to show that drop down, a drop down for roles. For a drop down we may require key (code) and label for each constant.
Challenge:
We have to implement these reference values in the database so we can change these reference values with out changing code (or without a deployment of code). Also we still would be able to refer them in code but the key and value would be coming from database.
Implementation:
Initially, I wanted to implement it as Enum but I could not. If someone can, I would happy to see implementation.
In this implementation I have introduced annotation (@ReferenceList) to map the reference type to database.
package com.zohaib.research.referenceTypes;
import com.zohaib.research.annotations.ReferenceList;
@ReferenceList("UserRoleNames")
public class UserRoleNames extends CommonReferenceTypeImplementation{
protected UserRoleNames(String constantName) {
super(constantName);
}
public static UserRoleNames
USER_ROLE = new UserRoleNames("USER_ROLE"),
ADMIN_ROLE = new UserRoleNames("ADMIN_ROLE"),
API_ROLE = new UserRoleNames("API_ROLE");
}
My reference types will have some common operations so we have got CommonReferenceTypeImplementation as follows:
package com.zohaib.research.referenceTypes;
public class CommonReferenceTypeImplementation implements ReferenceType{
private String code;
private String label;
private String name;
private boolean initialized;
protected CommonReferenceTypeImplementation(String constantName) {
this.name = constantName;
}
@Override
public void init(String code, String label){
this.code = code;
this.label = label;
this.initialized = true;
}
@Override
public String getCode() {
return this.code;
}
@Override
public String getLabel() {
return this.label;
}
@Override
public String name(){
return this.name;
}
@Override
public boolean hasBeenInitialized(){
return this.initialized;
}
}
An interface:
package com.zohaib.research.referenceTypes;
public interface ReferenceType {
public abstract String getCode();
public abstract String getLabel();
public void init(String code, String label);
public boolean hasBeenInitialized();
public String name();
}
Annotation:
package com.zohaib.research.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ReferenceList {
public String value();
}
Usage:
And I want to use that some thing like as follows:
@Controller
public class UserService {
@Autowired
private transient UserRepository userRepository;
@Autowired
private transient RefItemRepository refItemRepository;
@Autowired
private transient UserRoleRepository userRoleRepository;
protected transient Log logger = LogFactory.getLog(getClass());
@RequestMapping(value = "/userService/saveUser", method = RequestMethod.POST)
public @ResponseBody JSendResponse saveUser(@ModelAttribute User user) {
User savedUser = null;
try {
user.setUserName(user.getEmail());
user.setEnabled(Boolean.TRUE);
savedUser = userRepository.save(user);
// String role = UserRoleNames.USER_ROLE.getCode();
// TODO: Remove following fetch from above line in future.
/*
* RefItem userRoleRefItem =
* refItemRepository.findByName(UserRoleNames.ROLE_USER.toString());
* String role = userRoleRefItem.getCode();
*/
String role = UserRoleNames.USER_ROLE.getCode();
UserRole userRole = new UserRole();
userRole.setRoleName(role);
userRole.setUserId(savedUser.getId());
userRoleRepository.save(userRole);
} catch (Exception ex) {
logger.error("An Exception occurred in saving user!", ex);
return JSendResponse.applicationError(ex.getMessage());
}
return JSendResponse.success("userId", savedUser.getId());
}
}
Magic:
The actual magic is a Aspect:
package com.zohaib.research.aspects;
import javax.annotation.Resource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.hibernate.service.spi.InjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.stereotype.Component;
import com.zohaib.research.annotations.ReferenceList;
import com.zohaib.research.referenceTypes.ReferenceType;
import com.zohaib.research.relational.entities.RefItem;
import com.zohaib.research.repositories.jpa.RefItemRepository;
import com.zohaib.research.spring.SpringBeanUtils;
@Aspect
public class ReferenceTypeAspect {
protected transient Log logger = LogFactory.getLog(getClass());
@Before("execution(* com.zohaib.research.referenceTypes.CommonReferenceTypeImplementation.get*(..))")
public void initializeReferenceType(JoinPoint joinPoint) throws Exception{
if(logger.isDebugEnabled()){
logger.debug("calling initializeReferenceType aspect....");
}
if(joinPoint.getThis() != null && joinPoint.getThis() instanceof ReferenceType){
ReferenceType thisObject = (ReferenceType) joinPoint.getThis();
//Never ever call same method which you are already cross cutting. Otherwise infinite loop.
//if(thisObject.getCode() == null && thisObject.getLabel() == null){
//Lazy initialize reference item instance.
if(! thisObject.hasBeenInitialized()){
Class thisObjectClass = joinPoint.getThis().getClass();
ReferenceList refListAnnotationOnClass = thisObjectClass.getAnnotation(ReferenceList.class);
String referenceListName = refListAnnotationOnClass.value();
String referenceItemName = thisObject.name();
RefItemRepository refItemRepository = SpringBeanUtils.getBean(RefItemRepository.class);
RefItem refItem = refItemRepository.findByNameAndRefListName(referenceItemName, referenceListName);
thisObject.init(refItem.getCode(), refItem.getLabel());
}
}
}
}
We need aop.xml which would be in META-INF folder (src/main/resources/META-INF/aop.xml)
Model: Following is my model in JPA POJO:
package com.zohaib.research.relational.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name="RefList")
public class RefList extends AbstractEntity{
private static final long serialVersionUID = 7396124355136909774L;
@Column(name = "name",nullable=false)
private String name;
@Column(name = "description",nullable=false)
private String description;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
package com.zohaib.research.relational.entities;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name="RefItem")
public class RefItem extends AbstractEntity{
@Column(name = "name",nullable=false)
private String name;
@Column(name = "code",nullable=false)
private String code;
@Column(name = "label",nullable=false)
private String label;
@Column(name = "refListId",nullable=false)
private Long refListId;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "refListId",insertable=false,updatable=false)
private RefList refList;
@Column(name = "description")
private String description;
@Column(name = "archiveDate")
@Temporal(TemporalType.TIMESTAMP)
private Date archiveDate;
public RefList getRefList() {
return refList;
}
public void setRefList(RefList refList) {
this.refList = refList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public Long getRefListId() {
return refListId;
}
public void setRefListId(Long refListId) {
this.refListId = refListId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getArchiveDate() {
return archiveDate;
}
public void setArchiveDate(Date archiveDate) {
this.archiveDate = archiveDate;
}
}
package com.zohaib.research.relational.entities;
import java.io.Serializable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.apache.commons.lang.builder.ToStringBuilder;
@MappedSuperclass
public class AbstractEntity implements Serializable{
private static final long serialVersionUID = -992911032580505536L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* Returns the identifier of the entity.
*
* @return the id
*/
public Long getId() {
return id;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.id == null || obj == null
|| !(this.getClass().equals(obj.getClass()))) {
return false;
}
AbstractEntity that = (AbstractEntity) obj;
return this.id.equals(that.getId());
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this);
}
}
The hardest part:
Following is POM.xml with right maven plugin configuration. I am trying to use aspect with out spring dependency.
What Else: You may want to write implementations of repository but I am using Spring Data JPA repositories so I have got only interfaces, implementation will be generated by spring data:4.0.0 com.zohaib.research researchPrj war 1.0-SNAPSHOT Maven Webapp http://maven.apache.org Maven central http://central.maven.org/maven2 maven2 http://repo2.maven.org/maven2 maven.springframework http://maven.springframework.org/release spring-milestones http://repo.spring.io/milestone true true 4.0.3.RELEASE 1.5.2.RELEASE 3.2.3.RELEASE 4.3.5.Final 2.3.2 5.1.30 javax.servlet jstl 1.2 javax.servlet javax.servlet-api 3.0.1 provided org.springframework spring-webmvc ${org.springframework-version} org.springframework.data spring-data-jpa ${org.springData-version} org.springframework spring-tx ${org.springframework-version} org.aspectj aspectjrt 1.6.5 org.aspectj aspectjweaver 1.8.0.RELEASE org.springframework.security spring-security-web ${spring.security.version} org.springframework.security spring-security-config ${spring.security.version} org.springframework.security spring-security-taglibs ${spring.security.version} org.hibernate hibernate-core ${hibernate.version} org.hibernate hibernate-entitymanager ${hibernate.version} mysql mysql-connector-java ${mysql.connector.version} opensymphony sitemesh 2.4.2 cglib cglib-nodep 2.2 commons-collections commons-collections 3.1 commons-dbcp commons-dbcp 1.3 commons-beanutils commons-beanutils 1.8.3 org.apache.httpcomponents httpclient 4.1.1 commons-fileupload commons-fileupload 1.2.2 commons-io commons-io 1.4 commons-lang commons-lang 2.5 commons-pool commons-pool 1.5.4 org.codehaus.jackson jackson-mapper-asl 1.9.3 junit junit 3.8.1 test ROOT org.apache.maven.plugins maven-compiler-plugin 2.3.2 1.6 1.6 org.apache.maven.plugins maven-war-plugin 2.1.1 false org.codehaus.mojo aspectj-maven-plugin 1.5 1.6 1.6 1.6 compile test-compile org.apache.maven.plugins maven-site-plugin 3.0 org.codehaus.mojo cobertura-maven-plugin 2.5.1
package com.zohaib.research.repositories.jpa; import java.io.Serializable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; @NoRepositoryBean public interface JpaBasedRepositoryextends JpaRepository { } package com.zohaib.research.repositories.jpa; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.zohaib.research.relational.entities.RefItem; public interface RefItemRepository extends JpaBasedRepository { @Query("SELECT r FROM RefItem r WHERE r.name = :constantName") public RefItem findByName(@Param("constantName") String constantName); @Query("SELECT r FROM RefItem r inner join r.refList l WHERE r.name = :constantName and l.name = :refListName ") public RefItem findByNameAndRefListName( @Param("constantName") String constantName, @Param("refListName") String refListName ); } package com.zohaib.research.repositories.jpa; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import com.zohaib.research.relational.entities.RefList; public interface RefListRepository extends JpaBasedRepository { @Query("SELECT l FROM RefList l WHERE l.name = :refListName") public RefList findByName(@Param("refListName") String refListName); }
No comments:
Post a Comment
Zohaib: