I have existing model classes that always use builder pattern like this:
public class Model {
public static class Builder {
private boolean isValid;
private List<String> errorMessagesOrNull;
public Builder setIsValid(final boolean isValid) {
this.isValid = isValid;
return this;
}
public Builder setErrorMessages(final List<String> errorMessages) {
this.errorMessagesOrNull = errorMessages;
return this;
}
public List<String> getErrorMessages() {
return this.errorMessagesOrNull == null ? new ArrayList<>() : this.errorMessagesOrNull;
}
public Model Build() {
return new Model(this);
}
}
private boolean isValid;
private List<String> errorMessages;
private Model(final Builder builder) {
this.isValid = builder.isValid;
this.errorMessages = builder.getErrorMessages();
}
public boolean getIsValid() {
return isValid;
}
public List<String> getErrorMessages() {
return errorMessages;
}
}
As you see, the model classes always have isValid and errorMessages. I want to write an abstract class to minimize the repeated logic for those model classes.
So I came up like this abstract class:
public abstract class AbstractModel<T extends AbstractModel<T>> {
public static abstract class Builder<T> {
private boolean isValid;
private List<String> errorMessagesOrNull;
public Builder<T> setIsValid(final boolean isValid) {
this.isValid = isValid;
return this;
}
public Builder<T> setErrorMessages(final List<String> errorMessages) {
this.errorMessagesOrNull = errorMessages;
return this;
}
public List<String> getErrorMessages() {
return this.errorMessagesOrNull == null ? new ArrayList<>() : this.errorMessagesOrNull;
}
public abstract T Build();
}
private boolean isValid;
private List<String> errorMessages;
private AbstractModel(final Builder<T> builder) {
this.isValid = builder.isValid;
this.errorMessages = builder.getErrorMessages();
}
public boolean getIsValid() {
return isValid;
}
public List<String> getErrorMessages() {
return errorMessages;
}
}
But it's not really working as I intended. When I extends the abstract class:
public class Model extends AbstractModel<Model> {
// Empty here since all fields are extended
}
I cannot do something like:
Model model = new Model.Builder.setIsValid(true).Build();
I want the abstract class has Builder static class, so that I don't need to write the static class Builder every time.
Please advise.
You also need to implement the Builder.
public class Model extends AbstractModel<Model>{
private Model(final Builder builder) {
super(builder);
}
public static class Builder2 extends AbstractModel.Builder<Model> {
#Override
public Model Build() {
return new Model(this);
}
}
}
then it possible to call
Model model = new Model.Builder2().Build();
EDIT
Also, the constructor of AbstractBuilder also must be protected.
protected AbstractModel(final Builder<? extends Builder<T>> builder) {
this.isValid = builder.isValid;
this.errorMessages = builder.getErrorMessages();
}
I think that there is a huge flaw in your logic. The program itself doesn't really make any sense at all. Why do you construct a Model with the Builder class in the first place? I think it is better to show you how you should have written your program, instead of just "bodging" it together. Alright, let us start with the Model class.
Let's say the Model class cannot be constructed without a Builder. Would it then make sense to add the Builder class into the Model class? Short answer: no, it wouldn't. Instead, the Builder class should contain the Model class as a non-static internal class.
/**
* The {#code Builder} can construct new instances of the {#code Model} class.
*
* #see Model
*/
public class Builder
{
private final String[] log;
/**
* The {#code Model} class can do something. You can only construct it through a {#code Builder}.
*
* #see Builder
*/
public class Model
{
private final Builder builder;
/**
* Constructs a new {#code Model} with the specified argument.
*
* #param builder the {#code Builder} that constructed the model.
*/
public Model(final Builder builder)
{
this.builder = builder;
}
/**
* Returns the associated {#code Builder}.
*
* #return the builder that constructed the model.
*/
public Builder getBuilder()
{
return this.builder;
}
}
/**
* Constructs a new instance of the {#code Builder} class with the specified argument.
*
* #param log the log of the {#code Builder}.
*/
public Builder(final String... log)
{
this.log = log;
}
/**
* Tries to {#code build} a new instance of the {#code Model} class.
*
* #return the constructed {#code Model}.
*/
public Model build()
{
return new Model(this);
}
/**
* Returns the log of the {#code Builder}.
*
* #return an log.
*/
public String[] getLog()
{
return this.log;
}
/**
* Determines whether or not the {#code Builder} is valid.
*
* #return {#code true} when the specified {#code log} is not {#code null}; {#code false} otherwise.
*/
public boolean isValid()
{
return this.log != null;
}
}
No class other than the Builder can construct a Model. However, if you construct a new instance of the Builder class and get the result of invoking the build method, you'll have access to all public variables and methods.
If you know want to construct a Model, you can do that just like that:
Builder.Model model = new Builder().build();
If you don't want the Builder. prefix, just add an import statement that imports the Model class.
import organisation.projectname.pathToBuilder.Builder.Model;
I am developing a java web project using Spring and Mybatis.
In the dao level, I defined a super class and a super interface which implemented all common methods.
Thus when create sub class or interface for a specific model in dao level, I only need to implement the super dao class & interface, and left the class body and interface body empty.
Over half of the sub dao level class & interface is empty through all the time.
(Example of the empty dao class & interface:)
RoleDao.java
package core.dao;
import core.dao.base.BaseDao;
import core.model.Role;
public interface RoleDao extends BaseDao<Role> {
}
RoleDaoImpl.java
package core.dao.impl;
import org.springframework.stereotype.Repository;
import core.dao.RoleDao;
import core.dao.base.BaseDaoImpl;
import core.model.Role;
#Repository
public class RoleDaoImpl extends BaseDaoImpl<Role> implements RoleDao {
}
My question is:
Is there a good way to avoid writing these empty class & interface, while still could use them?
I am thinking of using Code generator to generate these class file, or use Java reflection to create such class & interface at runtime as need, but didn't get into detail yet.
#Update
It seems not flexible to achieve the target without creating source code, so I decided to write some simple java source code generator for java web project.
And a tool called codemodel is very suitable to do that, it is developed by Sun, and now owned by Oracle I guess.
And, I gave an answer by myself with code that I wrote to generate java source code.
The Repository classes for the classes in our projects that use QueryDSL and JPA only have an interface, but not an implementation. However, it does not answer the question whether it is possible to directly generate these repositories based on the entity classes, although it would be similar to what the Apt Maven Plugin does to create the QEntity classes for use with QueryDSL.
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
}
#Repository
public interface DummyDataRepository extends BaseRepository<DummyData, Long> {
}
About a month ago I was asking myself the same thing :)
So, seems that we have a kind of solution, since you are using Spring library. As I read on docs:
Rather than code data access objects (DAOs) manually using
SqlSessionDaoSupport or SqlSessionTemplate, Mybatis-Spring provides a
proxy factory: MapperFactoryBean. This class lets you inject data
mapper interfaces directly into your service beans. When using mappers
you simply call them as you have always called your DAOs, but you
won't need to code any DAO implementation because MyBatis-Spring will
create a proxy for you.
There's an example on GitHub and also on this MyBatis' page.
I hope that it gives you some insights, because maybe it isn't feasible refactoring your whole system to be benefited of such nice feature.
I just wrote a simple code generator for my project.
It's just a single class, and could generate model/dao/service/action level code template for 1 or more models in a single execution.
Dependence:
It use codemodel and apache commons-io lib, and it's a spring + springMVC project.
How to use it:
It import some base class/interface in my project, from which the generated class extends/implements from, so you might can't run it directly. But you can create them as empty class/interface, or remove them from the genSourceXxx() function.
CodeGenerator.java:
package my.project.util;
import my.project.dao.base.BaseDao;
import my.project.dao.base.BaseDaoImpl;
import my.project.model.base.BaseIdModel;
import my.project.service.base.BaseService;
import my.project.service.base.BaseServiceImpl;
import my.project.web.action.base.BaseAction;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
/**
* code generator
*
* #author eric
* #date Apr 10, 2015 3:32:57 PM
*/
public class CodeGenerator {
// location of source folder
public static final String tmpSourceFolderBaseLocation = "/tmp/java_code/"; // tmp location for generated code,
public static final String actualSourceFolderBaseLocation = "/mnt/star/workplace/eclipse_j2ee_workplace/project-name/source/java/"; // actual source folder,
// package
public static final String packageSeparator = ".";
public static final String basePackage = "my.project";
public static final String modelPackage = "model";
public static final String daoPackage = "dao";
public static final String daoImplPackage = "dao.impl";
public static final String servicePackage = "service";
public static final String serviceImplPackage = "service.impl";
public static final String actionPackage = "web.action";
// source file path
public static final String pkgPathSeparator = File.separator;
public static final String sourceSuffix = ".java";
public static final String basePkgPath = "my/project";
public static final String modelPkgPath = "model";
public static final String daoPkgPath = "dao";
public static final String daoImplPkgPath = "dao" + pkgPathSeparator + "impl";
public static final String servicePkgPath = "service";
public static final String serviceImplPkgPath = "service" + pkgPathSeparator + "impl";
public static final String actionPkgPath = "web" + pkgPathSeparator + "action";
// naming
public static final String daoSuffix = "Dao";
public static final String daoImplSuffix = "DaoImpl";
public static final String serviceSuffix = "Service";
public static final String serviceImplSuffix = "ServiceImpl";
public static final String actionSuffix = "Action";
// compiler for generated source code,
public static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// classloader for compiled class,
public static final ClassLoader cl = genCl(tmpSourceFolderBaseLocation);
/**
* compile a source file,
*
* #param sourcePath
* #throws MalformedURLException
*/
public static void compileSource(String sourcePath) throws MalformedURLException {
// set this so that won't get compile error,
System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + tmpSourceFolderBaseLocation);
compiler.run(null, null, null, sourcePath);
}
/**
* generate a classloader,
*
* #param path
* #return
* #throws MalformedURLException
*/
public static ClassLoader genCl(String path) {
ClassLoader cl = null;
try {
cl = new URLClassLoader(new URL[] { new File(path).toURI().toURL() });
} catch (MalformedURLException e) {
e.printStackTrace();
}
return cl;
}
/**
* <p>
* Generate source for model.
* </p>
*
* #param modelName
* #throws IOException
* #throws JClassAlreadyExistsException
*/
public static void genSourceModel(String modelName) throws IOException, JClassAlreadyExistsException {
String modelFullName = genFullName(modelPackage, modelName);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(modelFullName, ClassType.CLASS);
// extends
dc._extends(BaseIdModel.class);
// id
JFieldVar idField = dc.field(JMod.PRIVATE, Integer.class, "id"); // field
// id - getter method
JMethod getIdMethod = dc.method(JMod.PUBLIC, Integer.class, "getId");
getIdMethod.body()._return(idField);
getIdMethod.annotate(cm.ref(Override.class)); // annotation - override
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(modelPkgPath, modelName));
}
public static void genSourceDao(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String daoFullName = genFullName(daoPackage, modelName, daoSuffix);
String modelFullName = genFullName(modelPackage, modelName);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(daoFullName, ClassType.INTERFACE);
// extends
JClass superClazz = cm.ref(BaseDao.class).narrow(cl.loadClass(modelFullName));
dc._extends(superClazz);
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(daoPkgPath, modelName, daoSuffix));
}
public static void genSourceDaoImpl(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String daoImplFullName = genFullName(daoImplPackage, modelName, daoImplSuffix);
String daoFullName = genFullName(daoPackage, modelName, daoSuffix);
String modelFullName = genFullName(modelPackage, modelName);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(daoImplFullName, ClassType.CLASS);
dc.annotate(Repository.class);
// extends
JClass superClazz = cm.ref(BaseDaoImpl.class).narrow(cl.loadClass(modelFullName));
dc._extends(superClazz);
// implements
dc._implements(cl.loadClass(daoFullName));
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(daoImplPkgPath, modelName, daoImplSuffix));
}
public static void genSourceService(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String serviceFullName = genFullName(servicePackage, modelName, serviceSuffix);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(serviceFullName, ClassType.INTERFACE);
// extends
dc._extends(BaseService.class);
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(servicePkgPath, modelName, serviceSuffix));
}
public static void genSourceServiceImpl(String modelName, boolean serviceTransaction) throws JClassAlreadyExistsException, ClassNotFoundException,
IOException {
String serviceImplFullName = genFullName(serviceImplPackage, modelName, serviceImplSuffix);
String serviceFullName = genFullName(servicePackage, modelName, serviceSuffix);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(serviceImplFullName, ClassType.CLASS);
// annotation
dc.annotate(Service.class);
if (serviceTransaction) {
dc.annotate(Transactional.class);
}
// extends
dc._extends(BaseServiceImpl.class);
// implements
dc._implements(cl.loadClass(serviceFullName));
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(serviceImplPkgPath, modelName, serviceImplSuffix));
}
public static void genSourceAction(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
genSourceAction(modelName, null);
}
/**
* generate action,
*
* #param modelName
* #param rootMappingPath
* root mapping path, if null or empty then don't have this annotation,
* #throws JClassAlreadyExistsException
* #throws ClassNotFoundException
* #throws IOException
*/
public static void genSourceAction(String modelName, String rootMappingPath) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String actionFullName = genFullName(actionPackage, modelName, actionSuffix);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(actionFullName, ClassType.CLASS);
// annotation
dc.annotate(Controller.class);
if (StringUtils.isNotBlank(rootMappingPath)) {
dc.annotate(cm.ref(RequestMapping.class)).param("value", rootMappingPath);
}
// extends
dc._extends(BaseAction.class);
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(actionPkgPath, modelName, actionSuffix));
}
/**
* <p>
* generate a serial java source code base on a single model, don't include service level,
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*
* #param modelName
*/
public static void genStack(String modelName) {
genStack(modelName, false, false, null);
}
/**
* <p>
* generate a serial java source code base on a single model.
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*
* #param modelName
* #param includeService
* specify whether include service level,
* #param serviceTransaction
* whether add transaction annotation to service impl class,
* #param actionRootMappingPath
* root mapping path, if null or empty then don't have this annotation,
*/
public static void genStack(String modelName, boolean includeService, boolean serviceTransaction, String actionRootMappingPath) {
try {
initTmp(); // clean or create folder,
// generate code - start
genSourceModel(modelName);
genSourceDao(modelName);
genSourceDaoImpl(modelName);
if (includeService) {
genSourceService(modelName);
genSourceServiceImpl(modelName, serviceTransaction);
}
genSourceAction(modelName, actionRootMappingPath);
// generate code - end
merge(); // copy,
initTmp(); // clean, so that won't have duplicated class,
} catch (IOException | JClassAlreadyExistsException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* <p>
* batch generate.
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*
* #param models
* map of "modelName : actionRootMappingPath"
* #param includeService
* specify whether include service level,
* #param serviceTransaction
* whether add transaction annotation to service impl class,
*/
public static void genStackBatch(Map<String, String> models, boolean includeService, boolean serviceTransaction) {
for (String modelName : models.keySet()) {
genStack(modelName, includeService, serviceTransaction, models.get(modelName));
}
}
/**
* generate class fullname,
*
* #param subPackage
* #param modelName
* #return
*/
public static String genFullName(String subPackage, String modelName) {
return genFullName(subPackage, modelName, "");
}
/**
* generate class fullname,
*
* #param subPackage
* #param modelName
* #param suffix
* #return
*/
public static String genFullName(String subPackage, String modelName, String suffix) {
return new StringBuilder().append(basePackage).append(packageSeparator).append(subPackage).append(packageSeparator).append(modelName).append(suffix)
.toString();
}
/**
* generate source file path,
*
* #param subPkgPath
* #param modelName
* #return
*/
public static String genFullPath(String subPkgPath, String modelName) {
return genFullPath(subPkgPath, modelName, "");
}
/**
* generate source file path,
*
* #param subPkgPath
* #param modelName
* #param suffix
* #return
*/
public static String genFullPath(String subPkgPath, String modelName, String suffix) {
return new StringBuilder().append(tmpSourceFolderBaseLocation).append(basePkgPath).append(pkgPathSeparator).append(subPkgPath).append(pkgPathSeparator)
.append(modelName).append(suffix).append(sourceSuffix).toString();
}
/**
* clean tmp location,
*
* #throws IOException
*/
public static void initTmp() throws IOException {
File tmp = new File(tmpSourceFolderBaseLocation);
if (!tmp.exists()) { // create if not exists,
tmp.mkdirs();
} else { // clean if exists,
FileUtils.cleanDirectory(tmp);
}
}
/**
* <p>
* move generated code into source folder,
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*/
public static void merge() {
File originalFile = new File(tmpSourceFolderBaseLocation + basePkgPath);
File targetFile = new File(actualSourceFolderBaseLocation + basePkgPath);
try {
// filter - java file,
IOFileFilter javaSuffixFilter = FileFilterUtils.suffixFileFilter(".java");
IOFileFilter javaFiles = FileFilterUtils.and(FileFileFilter.FILE, javaSuffixFilter);
// filter - dir or java file,
FileFilter filter = FileFilterUtils.or(DirectoryFileFilter.DIRECTORY, javaFiles);
FileUtils.copyDirectory(originalFile, targetFile, filter);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// String modelName = "LoginHistory";
// String actionRootMappingPath = "/loginHistory";
// genStack(modelName, true, false, actionRootMappingPath);
Map<String, String> models = new HashMap<String, String>();
models.put("AdminAccount", "/adminAccount");
models.put("CustomerAccount", "/customerAccount");
models.put("Role", "/role");
genStackBatch(models, true, true);
}
}
I am trying to create a a custom method for use in Pre/Post Authorize calls like this:
public class CustomLSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler{
public CustomSecurityExpressionHandler(){
super();
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation){
CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
return root;
}
}
and
public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private Object target;
public CustomSecurityExpressionRoot(Authentication a) {
super(a);
}
public boolean testDecision(String test){
System.out.println("Printing:"+test+"\n");
return true;
}
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
public Object getFilterObject() {
return filterObject;
}
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
public Object getReturnObject() {
return returnObject;
}
void setThis(Object target) {
this.target = target;
}
public Object getThis() {
return target;
}
public boolean hasPermission(Object permission) {
try {
return super.hasPermission(null, null, permission);
} catch (AccessDeniedException e) {
return false;
}
}
public boolean checkPermission(Object permission) {
return super.hasPermission(null, null, permission);
}
#Override
public boolean hasPermission(Object targetId, String targetType, Object permission) {
try {
return super.hasPermission(targetId, targetType, permission);
} catch (AccessDeniedException e) {
return false;
}
}
public boolean checkPermission(Object targetId, String targetType, Object permission) {
return super.hasPermission(targetId, targetType, permission);
}
#Override
public boolean hasPermission(Object target, Object permission) {
try {
return super.hasPermission(target, permission);
} catch (AccessDeniedException e) {
return false;
}
}
public boolean checkPermission(Object target, Object permission) {
return super.hasPermission(target, permission);
}
}
As seen above I have added the new method testDecision(String), which I can successfully use in my preAuthorize call as below:
#PreAuthorize("testDecision('TestString')")
Event getEvent(int eventId);
But when I call it in the context of a PostAuthorize as:
#PostAuthorize("testDecision('TestString')")
Event getEvent(int eventId);
I get a ClassCastException:
SEVERE: Servlet.service() for servlet [Spring MVC Dispatcher Servlet] in context with path [/myapp] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: com.example.CustomSecurityExpressionRoot cannot be cast to org.springframework.security.access.expression.method.MethodSecurityExpressionRoot] with root cause
java.lang.ClassCastException: com.example.CustomSecurityExpressionRoot cannot be cast to org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
at org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler.setReturnObject(DefaultMethodSecurityExpressionHandler.java:156)
at org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice.after(ExpressionBasedPostInvocationAdvice.java:49)
at org.springframework.security.access.prepost.PostInvocationAdviceProvider.decide(PostInvocationAdviceProvider.java:38)
at org.springframework.security.access.intercept.AfterInvocationProviderManager.decide(AfterInvocationProviderManager.java:73)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.afterInvocation(AbstractSecurityInterceptor.java:282)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:68)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy15.getEvent(Unknown Source)
(..truncated..)
Anyone can help me figure out what I am doing wrong?
It seems you are on an older version of Spring Security. As of Spring Security 3.1.5+ SEC-2245 is fixed & you can create your own expression root and implement MethodSecurityExpressionOperations.
The class CustomSecurityExpressionRoot must extends MethodSecurityExpressionRoot!
(implementing MethodSecurityExpressionOperations) is not enough.
Unfortunately MethodSecurityExpressionRoot is a package protected class.
Therfore you need to put CustomSecurityExpressionRoot in the same package (org.springframework.security.access.expression.method)
or you use the following class as super class for your CustomSecurityExpressionRoot (that is what I do in my projects)
ExtensibleMethodSecurityExpressionRoot:
package org.springframework.security.access.expression.method;
import org.springframework.security.core.Authentication;
/** Makes the class {#link MethodSecurityExpressionRoot} public to other packages. */
public class ExtensibleMethodSecurityExpressionRoot extends MethodSecurityExpressionRoot {
/**
* Instantiates a new extensible method security expression root.
* #param a the Authentication
*/
public ExtensibleMethodSecurityExpressionRoot(final Authentication a) {
super(a);
}
}
My complete way is this:
ExtensibleMethodSecurityExpressionHandler to change the evaluation root context:
package org.springframework.security.access.expression.method;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.defaultexpression.DefaultMethodSecuritiyExpressionRootFactory;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
/**
* This class is the same like {#link MethodSecurityExpressionHandler} but its evaluation
* root context can be exchanged.
* To use an other evaluation root context, set an other {#link #methodSecurityExpRootFactory}.
*
*/
public class ExtensibleMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
/** The parameter name discoverer. */
private ParameterNameDiscoverer parameterNameDiscoverer;
/** The permission evaluator. */
private PermissionEvaluator permissionEvaluator;
/** The trust resolver. */
private AuthenticationTrustResolver trustResolver;
/** The expression parser. */
private ExpressionParser expressionParser;
/** The method security expression root factory. */
private MethodSecurityExpressionRootFactory<?> methodSecurityExpRootFactory;
/** The role hierarchy. */
private RoleHierarchy roleHierarchy;
/**
* Instantiates a new extensible method security expression handler.
*/
public ExtensibleMethodSecurityExpressionHandler() {
this.parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
this.permissionEvaluator = new DenyAllPermissionEvaluator();
this.trustResolver = new AuthenticationTrustResolverImpl();
this.expressionParser = new SpelExpressionParser();
this.methodSecurityExpRootFactory = new DefaultMethodSecuritiyExpressionRootFactory();
}
/**
* Uses a {#link MethodSecurityEvaluationContext} as the <tt>EvaluationContext</tt> implementation and
* configures it with a {#link MethodSecurityExpressionRoot} instance as the expression root object.
*
* #param auth the auth
* #param mi the mi
* #return the evaluation context
*/
#Override
public EvaluationContext createEvaluationContext(final Authentication auth, final MethodInvocation mi) {
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth,
mi,
this.parameterNameDiscoverer);
MethodSecurityExpressionRoot root = this.methodSecurityExpRootFactory.createMethodSecurityExpressionRoot(auth);
root.setTrustResolver(this.trustResolver);
root.setPermissionEvaluator(this.permissionEvaluator);
root.setRoleHierarchy(this.roleHierarchy);
ctx.setRootObject(root);
return ctx;
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.security.access.expression.method.MethodSecurityExpressionHandler#filter(java.lang.Object,
* org.springframework.expression.Expression, org.springframework.expression.EvaluationContext)
*/
#Override
#SuppressWarnings({ "unchecked", "rawtypes" })
public Object filter(final Object filterTarget, final Expression filterExpression, final EvaluationContext ctx) {
MethodSecurityExpressionRoot rootObject = (MethodSecurityExpressionRoot) ctx.getRootObject().getValue();
List retainList;
if (filterTarget instanceof Collection) {
Collection collection = (Collection) filterTarget;
retainList = new ArrayList(collection.size());
for (Object filterObject : (Collection) filterTarget) {
rootObject.setFilterObject(filterObject);
if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
retainList.add(filterObject);
}
}
collection.clear();
collection.addAll(retainList);
return filterTarget;
}
if (filterTarget.getClass().isArray()) {
Object[] array = (Object[]) filterTarget;
retainList = new ArrayList(array.length);
for (Object element : array) {
rootObject.setFilterObject(element);
if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
retainList.add(element);
}
}
Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass().getComponentType(),
retainList.size());
for (int i = 0; i < retainList.size(); i++) {
filtered[i] = retainList.get(i);
}
return filtered;
}
throw new IllegalArgumentException("Filter target must be a collection or array type, but was " + filterTarget);
}
/*
* (non-Javadoc)
*
* #see org.springframework.security.access.expression.method.MethodSecurityExpressionHandler#getExpressionParser()
*/
#Override
public ExpressionParser getExpressionParser() {
return this.expressionParser;
}
/**
* Sets the parameter name discoverer.
*
* #param parameterNameDiscoverer the new parameter name discoverer
*/
public void setParameterNameDiscoverer(final ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Sets the permission evaluator.
*
* #param permissionEvaluator the new permission evaluator
*/
public void setPermissionEvaluator(final PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
/**
* Sets the trust resolver.
*
* #param trustResolver the new trust resolver
*/
public void setTrustResolver(final AuthenticationTrustResolver trustResolver) {
this.trustResolver = trustResolver;
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.security.access.expression.method.MethodSecurityExpressionHandler#setReturnObject(java.lang
* .Object, org.springframework.expression.EvaluationContext)
*/
#Override
public void setReturnObject(final Object returnObject, final EvaluationContext ctx) {
((MethodSecurityExpressionRoot) ctx.getRootObject().getValue()).setReturnObject(returnObject);
}
/**
* Sets the role hierarchy.
*
* #param roleHierarchy the new role hierarchy
*/
public void setRoleHierarchy(final RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}
/**
* Gets the method security expression root factory.
*
* #return the method security expression root factory
*/
public MethodSecurityExpressionRootFactory<?> getMethodSecurityExpressionRootFactory() {
return this.methodSecurityExpRootFactory;
}
/**
* Sets the method security expression root factory.
*
* #param methodSecurityExpressionRootFactory the new method security expression root factory
*/
public void setMethodSecurityExpressionRootFactory(
final MethodSecurityExpressionRootFactory<?> methodSecurityExpressionRootFactory) {
this.methodSecurityExpRootFactory = methodSecurityExpressionRootFactory;
}
/**
* Gets the parameter name discoverer.
*
* #return the parameter name discoverer
*/
public ParameterNameDiscoverer getParameterNameDiscoverer() {
return this.parameterNameDiscoverer;
}
/**
* Gets the permission evaluator.
*
* #return the permission evaluator
*/
public PermissionEvaluator getPermissionEvaluator() {
return this.permissionEvaluator;
}
/**
* Gets the trust resolver.
*
* #return the trust resolver
*/
public AuthenticationTrustResolver getTrustResolver() {
return this.trustResolver;
}
/**
* Gets the role hierarchy.
*
* #return the role hierarchy
*/
public RoleHierarchy getRoleHierarchy() {
return this.roleHierarchy;
}
/**
* Sets the expression parser.
*
* #param expressionParser the new expression parser
*/
public void setExpressionParser(final ExpressionParser expressionParser) {
this.expressionParser = expressionParser;
}
}
MethodSecurityExpressionRootFactory:
package org.springframework.security.access.expression.method;
import org.springframework.security.core.Authentication;
/**
* Factory Class/Template Class-Pattern: Template Class interface to create different expression root objects.
*
* #param <T> the {#link ExtensibleMethodSecurityExpressionRoot} created by this factory.
*/
public interface MethodSecurityExpressionRootFactory<T extends ExtensibleMethodSecurityExpressionRoot> {
/**
* Creates a new MethodSecurityExpressionRoot object.
*
* #param authentication the authentication
* #return the extensible method security expression root
*/
T createMethodSecurityExpressionRoot(final Authentication authentication);
}
DefaultMethodSecuritiyExpressionRootFactory: only needed if one want to use the ExtensibleMethodSecurityExpression handler without own extension
package org.springframework.security.access.expression.method.defaultexpression;
import org.springframework.security.access.expression.method.ExtensibleMethodSecurityExpressionRoot;
import org.springframework.security.access.expression.method.MethodSecurityExpressionRootFactory;
import org.springframework.security.core.Authentication;
/**
* Create the default {#link ExtensibleMethodSecurityExpressionRoot} expression root.
*/
public class DefaultMethodSecuritiyExpressionRootFactory implements
MethodSecurityExpressionRootFactory<ExtensibleMethodSecurityExpressionRoot> {
#Override
public ExtensibleMethodSecurityExpressionRoot createMethodSecurityExpressionRoot(final Authentication auth) {
return new ExtensibleMethodSecurityExpressionRoot(auth);
}
}
Example Customized Method Expression Root
package com.queomedia.vwcotool.infrastructure.security.spring;
import org.springframework.security.access.expression.method.ExtensibleMethodSecurityExpressionRoot;
import org.springframework.security.core.Authentication;
public class VwCoToolMethodSecurityExpressionRoot extends ExtensibleMethodSecurityExpressionRoot {
private Authentication a;
public MyMethodSecurityExpressionRoot(final Authentication a) {
super(a);
this.a = a;
}
public isXXX(final DomainObject x){
return x.getCreator().getName().equals(a.getPrincipal());
}
}