since one day I'm stuck at this Problem. But first I would like to describe, why I'm going the way which is shown:
We`re building a RESTful API in Java using EE7 and Glassfish4. Authentication and authorisation has to be build by ourselves (student project). So the idea was to add our own annotation for #AccesRight and #Roles. After interpreting the metadata on each set and get method of our models (if declared), the #XmlTransient annotation should be set on runtime, when the user has not the right to see this. In short: granting different access at the model attributes.
I tried to modify the method annotations from _model-class-methods (see method signature) but when I run ".toClass()" it fails, because the WebAppClassLoader already has a class loaded (duplicate entry). So I decided to create a copy with another name of the given model (_model.getClass().getName() + transactionToken). The big problem: I can not cast this copy anymore to the original model (I get ClassCastException). The class and the copyclass is stored in the same class loader.
So i considered to invoke a method like e.g. "loadModelByEntity(UserModel _model)" which is stored in all models. The problem is: after running .toClass() of my copy class the method signature now looks like the following: loadModelByEntity(UserModel020a8e6bb07c65da3e9095368db34e843c0b0d1e _model)
Javassist is changing ALL datatypes in the class.
Is there any way to prevent this or to fill my copy model with data? Is there any way to cast my copy models?
Many thanks!
Phil
//my annotation interface
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.TYPE, ElementType.METHOD} )
public #interface AccessRight
{
String name() default "";
Role[] roles() default {};
boolean self() default true;
boolean friends() default true;
}
//my method where i analyse the annotation (and perhaps set a new one)
public Object filter(Object _model, String _transactionToken) throws Exception
{
String className = _model.getClass().getName() + transactionToken;
ClassPool pool = ClassPool.getDefault();
CtClass copyClass = pool.getOrNull(className);
if(copyClass != null)
{
Class filterModel = copyClass.getClass().getClassLoader().loadClass(className);
Object filterInstance = filterModel.newInstance();
filterInstance.getClass().getDeclaredMethod("loadByEntity", _model.getClass()).invoke(filterInstance, _model);
return filterInstance;
}
pool.insertClassPath(new ClassClassPath(_model.getClass()));
pool.makeClass(className);
copyClass = pool.getAndRename(_model.getClass().getName(), className);
ClassFile copyClassFile = copyClass.getClassFile();
ConstPool constPool = copyClassFile.getConstPool();
AnnotationsAttribute attribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation annotation = attribute.getAnnotation("javax.xml.bind.annotation.XmlTransient");
if(annotation == null)
{
attribute.addAnnotation(new Annotation("javax.xml.bind.annotation.XmlTransient", constPool));
}
for(CtMethod method : copyClass.getDeclaredMethods())
{
if(method.hasAnnotation(AccessRight.class))
{
AccessRight arAnnotation = (AccessRight)method.getAnnotation(AccessRight.class);
if(!checkAccess(arAnnotation.name(), arAnnotation.roles(), arAnnotation.friends(), arAnnotation.self()))
{
method.getMethodInfo().addAttribute(attribute);
}
}
}
return copyClass.toClass().newInstance();
}
//my consideration to fill the copy model (but it doesn`t work, like i described)
public void loadByEntity(UserModel _model)
{
this.m_id = _model.getId();
this.m_firstname = _model.getFirstname();
this.m_lastname = _model.getLastname();
this.m_username = _model.getUsername();
this.m_birthday = _model.getBirthday();
this.m_email = _model.getEmail();
this.m_password = _model.getPassword();
this.m_roleId = _model.getRoleId();
this.m_timestampCreated = _model.getTimestampCreated();
this.m_accessRightList = _model.getAccesRightList();
}
I solved the problem by deleting the method "loadByEntity" (this is Settings.ENTITY_LOAD_METHODNAME) on runtime in the copy class. Then I readded the method to the copy class with a custom Signature and the javassist codeAttribute from the original class. Also I added the original class as the superclass for casting issues. So my signature looks nice and I`m able to cast to the original model. The methods are ALL overwritten now, because the signature is the same.
String className = _model.getClass().getName() + _transactionToken + Helper.getUnixTimestamp() / Math.random();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(_model.getClass()));
CtClass copyClass = pool.getAndRename(_model.getClass().getName(),className);
CtClass originalClass = pool.get(_model.getClass().getName());
ClassFile copyClassFile = copyClass.getClassFile();
ConstPool constPool = copyClassFile.getConstPool();
copyClass.setSuperclass(pool.get(_model.getClass().getName()));
copyClass.removeMethod(copyClass.getDeclaredMethod(Settings.ENTITY_LOAD_METHODNAME));
//creates a new method without codeattribute BUT(!) it is abstract
CtMethod newLoadMethod = new CtMethod(CtClass.voidType, Settings.ENTITY_LOAD_METHODNAME, new CtClass[] {originalClass}, copyClass);
CtMethod oldLoadMethod = originalClass.getDeclaredMethod(Settings.ENTITY_LOAD_METHODNAME);
//set modifier to NOT abstract
newLoadMethod.setModifiers(newLoadMethod.getModifiers() & ~Modifier.ABSTRACT);
//set the old code attribute
newLoadMethod.getMethodInfo().setCodeAttribute(oldLoadMethod.getMethodInfo().getCodeAttribute());
copyClass.addMethod(newLoadMethod);
Related
I am trying to come up with a custom annotation, wanted to see if my use-case fit a allowed way of using custom annotation.
I want to replicate what Spring #Value does, but instead of reading a property off of a property, i want to my custom thing.
#Documented
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public #interface EncryptedValue {
String value();
}
public Class TestEncrypted {
#EncryptedValue("dGVzdCBzdHJpbmc=");
public String someEncryptedValue;
}
I am hoping in annotation processor, i decrypt value and set to the field someEncryptedValue.
/**
*
*/
#SupportedAnnotationTypes("annotation.EncryptedValue")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomProcessor extends AbstractProcessor{
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
for(Element ele : annotatedElements) {
EncryptedValue encryptedValue = ele.getAnnotation(EncryptedValue.class);
if(!ele.getKind().isField()){
messager.printMessage(Diagnostic.Kind.ERROR,"EncryptedValue is supported for field");
return false;
}
String annotationValue = encryptedValue.value();
// now get the enclosing type
Set<Modifier> modifiers = ele.getModifiers();
String nameOfVariable = ele.getSimpleName().toString();
// check to see what fields we can modify (i think we can't modify static).
messager.printMessage(Diagnostic.Kind.NOTE,"ClassType: "+ele.getSimpleName().toString()+", nameOf="+annotationValue);
String simpleName = ele.getEnclosingElement().getSimpleName().toString();
for (Element elem : roundEnv.getRootElements()) {
messager.printMessage(Diagnostic.Kind.NOTE, "Enclosing ClassName: "+elem.getSimpleName().toString());
if (elem.getSimpleName().toString().equals(simpleName)) {
for (Element variableDeclaration : elem.getEnclosedElements()) {
if (variableDeclaration instanceof VariableElement) {
messager.printMessage(Diagnostic.Kind.NOTE, "variable: "+((VariableElement) variableDeclaration).getSimpleName().toString());
}
}
}
}
}
}
return true;
}
}
I get the variable, its return types and everything, but not sure how to set value of the variable from this annotation, even if i figure it out, is it good way of using custom annotations.
*Note: This might be sample, what I am planning to do is much more complicated than above sample.
There's no way to modify existing source files via the current publicly-available API. Tools like Lombok which do this are using undocumented internal Javac features to edit the abstract syntax tree. For example, you could use the Sun compiler tree API to obtain a VariableTree, cast it to a JCVariableDecl, then modify it and hope there are no unforeseen consequences. There's no guarantee that tools like Lombok will actually work, and they could break tomorrow with no warning.
What you could do instead is have the annotated classes reference a class which your annotation processor generates, as in the following example:
public class TestEncrypted {
#EncryptedValue("dGVzdCBzdHJpbmc=");
public String someEncryptedValue =
TestEncryptedDecryptedValues.someEncryptedValue;
}
// then generate this class with the annotation processor
final class TestEncryptedDecryptedValues {
static final String someEncryptedValue = "test string";
}
Another way to do something like this would be to use the annotation processor to generate a factory object or method which creates instances of e.g. TestEncrypted with the field assigned to the decrypted value.
A good tutorial for code generation with annotation processors is here: https://deors.wordpress.com/2011/10/08/annotation-processors/
Also, as a side note in case you don't know this, String literals and names appear in the compiled class file, so none of these examples which decrypt the data at compile-time provide any security.
An image taken from a book which I am going through,
The caption says it all. Please suggest or give me something as to what happens behind the scenes.
For example, how does #NotNull in Hibernate Bean Validation API works?
I know that through Reflection API, we can do something like this,
class Meta {
// Annotate a method.
#MyAnno(str = "Annotation Example", val = 100)
public static void myMeth() {
Meta ob = new Meta();
// Obtain the annotation for this method
// and display the values of the members.
try {
// First, get a Class object that represents
// this class.
Class c = ob.getClass();
// Now, get a Method object that represents
// this method.
Method m = c.getMethod("myMeth");
// Next, get the annotation for this class.
MyAnno anno = m.getAnnotation(MyAnno.class);
// Finally, display the values.
System.out.println(anno.str() + " " + anno.val());
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
Annotations don't have any implemented code and actually don't do anything themself.
To make them "work", there should be some kind of annotation processor (initializer, loader or any class that works with annotated objects). This annotation processor checks annotation objects annotations and changes the way it is handled.
For example Spring annotation processor, when initializing an object, looks for #Autowired fields, to fill autowired fields.
Same goes for Hibernates #NotNull. it doesn't do anything actually. However, Hibernate, when persisting your object, checks if there should be something there.
I would like to use a custom IntegerMetaClass only in a given GroovyShell context.
The reason why is to not pollute the whole runtime with my potentially 'disturbing' IntegerMetaClass.
It works like a charm when I put my IntegerMetaClass.java implementation in the magic package groovy.runtime.metaclass.java.lang. But, when I try to add it manually to an intermediate GroovyClassLoader, it stops working.
// Pseudo-code without try catch, etc
// Within my eval factory
GroovyClassLoader gcl = new GroovyClassLoader(getClass().getClassLoader());
URL url = getClass().getClassLoader().getResource("groovy/runtime/metaclass/java/lang/IntegerMetaClass.groovy"); // I rename it to .groovy file
GroovyCodeSource gcs = new GroovyCodeSource(url);
Class<?> clazz = gcl.parseClass(gcs);
// clazz is not null here and equals to what I expect:
// Class<groovy.runtime.metaclass.java.lang.IntegerMetaClass>
// Now trying to use it in a groovy shell
GroovyShell gs = new GroovyShell(gcl);
gs.evaluate("10.minutes"); // Where .minutes is part of my IntegerMetaClass
// Fail with an NoSuchProperty exception
Do I miss something to do on GroovyClassLoader more than just 'parsing' the MetaClass ? Anywhere else ?
Update1:
As mentioned above, IntegerMetaClass.minutes lookup is working when I put it directly in my java sources classpath.
package groovy.runtime.metaclass.java.lang;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.MetaClass;
public class IntegerMetaClass extends DelegatingMetaClass {
public IntegerMetaClass(Class<Integer> delegate) {
super(delegate);
}
public IntegerMetaClass(MetaClass delegate) {
super(delegate);
}
#Override
public Object getProperty(Object object, String property) {
if ("minutes".equals(property)) {
Integer q = (Integer) object;
return new Minutes(q);
}
return super.getProperty(object, property);
}
}
Update2:
A possible but not satisfying solution :
Adding the following just after the gcl.parseClass call
Constructor<?> constructor = clazz.getConstructor(Class.class);
DelegatingMetaClass dmc = (DelegatingMetaClass) constructor.newInstance(Integer.class);
dmc.initialize();
InvokerHelper.getMetaRegistry().setMetaClass(Integer.class, dmc);
But this solution has to maintain a sort of 'mapping' between MetaClass sources and original targeted class to support more than Integer ...
You can load the class with the GroovyShell classloader:
GroovyShell gs = new GroovyShell()
gs.loader.loadClass('groovy.runtime.metaclass.java.lang.IntegerMetaClass')
gs.evaluate("10.minutes")
Note: I got a java.lang.IncompatibleClassChangeError when IntegerMetaClass was a groovy file but no issues when it was java. That could just be related to my environment setup
I am required to read all classes(interfaces) decorated with a particular annotation (say #HttpSecurity) at runtime. After scanning through, I wish to read and parse the fields(enum fields) of class decorated with annotations. For eg.
#HttpSecurity
public interface MyHttpSecurityConfig {
public enum secure {
#Path(pathGroup = "/*", pathName = "")
#Authc
#Form(errorPage = "/error.html", loginPage = "/login.html", restoreOriginalRequest = "")
#Authz
#AllowedRoles(roles = { "roleA", "roleB" })
#AllowedGroups(groups = { "groupA" })
#AllowedRealms(realms = { "realmA" })
#Expressions(expressions = { "#{identity.isLoggedIn()}" })
Admin
}
}
There might be one or more classes/interfaces decorated with #HttpSecurity. My first requirement is to fetch all such classes and second requirement is to build up HttpSecurityBuilder by reading the annotations and their values decorated on enum field(s).
The second requirement is fine and can be done away using reflections. But, my problem is the first requirement. I want to achieve first requirement with JavaSE core i.e., without using any external dependency like google reflections. It might be assumed,if necessary, that we have the package name in which classes are to be scanned. Here is what I did usiNG cdi
You can create a CDI Extension that observes the scan from CDI Annotations and create you customization, as the example below:
1) You need to create a Qualifier, by using you #HttpSecurity
#Qualifier
#Retention(RUNTIME)
#Target({TYPE, METHOD, FIELD, PARAMETER})
public #interface HttpSecurity {}
2) You need to create a extension by implementing the interface javax.enterprise.inject.spi.Extension:
package net.mperon.cdi.extension;
public class MyExtension implements Extension {
private static final Logger log = LoggerFactory.getLogger(MyExtension.class);
public <T> void processAnnotatedType(#Observes ProcessAnnotatedType<T> pat) {
AnnotatedType<T> at = pat.getAnnotatedType();
//if dont have you anotation, just continue
if(!at.isAnnotationPresent(HttpSecurity.class)) {
return;
}
//here you can read all annotation from object and do whatever you want:
log.info("class: {}", at.getJavaClass());
log.info("constructors: {}", at.getConstructors());
log.info("fields: {}", at.getFields());
log.info("methods: {}", at.getMethods());
//and so more...
}
}
3) You can see all methods and properties here
4) At last you need to create a service file, under META-INF/services named javax.enterprise.inject.spi.Extension
5) Inside this text file, you need to put you extension full class name, by example:
net.mperon.cdi.extension.MyExtension
Unfortunately, Java doesn't provide an easy way to list classes in the "native" JRE. my favourite solution is the Google Reflections library, but if you don't want to use it there are other ways. One way would be to find the jar or jars in question and scan them for annotations on class files. This is achieved as follows:
// Jars are really just zip files with a different name
ZipInputStream zip = new ZipInputStream(new FileInputStream("/path/to/jar/file.jar"));
for(ZipEntry entry=zip.getNextEntry(); entry!=null; entry=zip.getNextEntry()) {
String name = entry.getName();
// We only want class files
if(name.endsWith(".class") && !entry.isDirectory()) {
// Remove the .class extension
name = name.substring(0, name.length() - 6);
// Replace the slashes in the path with '.'
name.replaceAll("/",".");
// Get the class object so we can use reflection on it
Class<?> cls = Class.forName(name);
}
}
Well guys; Here's my problem:
I currently have a class extending another that will have a gameinfo.class annotation on each class (Is there a way I can enforce this with the compiler?)
Ideally I'd like to call the following in the type it is extending:
#GameInfo(name = "Default Minigame", aliases = {"DM"}, pvp = false, authors = {"Tom"},
gameTime = 65, description = "desc")
public class DefaultMinigame extends Minigame {
public DefaultMinigame(Plugin p) {
super(p, DefaultMinigame.class.getAnnotation(GameInfo.class));
}
I'd rather not have to call the above in each class and instead call it in Minigame.class
How would I do this without calling "this" (Due to super type not being initialized)
Thanks for your time!
(And sorry if what I'm saying isn't the most comprehensible of questions)
You can not enforce that a subclass has an annotation. If that is important to you, you might use the template method pattern instead.
But if you need to use annotations, you can read the subclass annotation with
class Minigame {
protected Minigame() {
GameInfo info = getClass().getAnnotation(GameInfo.class);
}
}