clojure gen-class with annotation that require enum parameter - java

I want to write a clojure lib and exposes generated classes, so that other java projects could use it.
I read and followed the gen-class doc, and everything works as my expect, except class annotation with enum parameters.
(ns common.exception.unauthorized
(:gen-class :name
^{org.springframework.web.bind.annotation.ResponseStatus
org.springframework.http.HttpStatus/UNAUTHORIZED} ; <- here
com.lalala.common.exception.Unauthorized
:extends java.lang.RuntimeException
:init init
:constructors {[String] [String]}
:main false
:prefix "unauthorized-"))
(defn unauthorized-init [^String message]
[[message] nil])
This exception class is generated without any error, and it also able be used as an Exception. However, this exception is intended to be used as a http response along with spring web. The spring framework read the annotation, and find that it is HttpStatus/UNAUTHORIZED then response 401. But the spring framework throws exception complaining that java.lang.EnumConstantNotPresentException: org.springframework.http.HttpStatus.
And I had a look at the generated class, it's something like this:
#ResponseStatus(HttpStatus.401)
public class Unauthorized extends RuntimeException {
private static final Var init__var = Var.internPrivate("common.exception.unauthorized", "unauthorized-init");
private static final Var getStackTrace__var = Var.internPrivate("common.exception.unauthorized", "unauthorized-getStackTrace");
// ...... ellipsis
}
As it shown, the HttpStatus/UNAUTHORIZED is compiled into HttpStatus.401 which is invalid.
I also tried with {:code org.springframework.http.HttpStatus/UNAUTHORIZED}, {:value org.springframework.http.HttpStatus/UNAUTHORIZED}, it can be compiled into #ResponseStatus(code/value = HttpStatus.401), but the enum value itself still in invalid form HttpStatus.401.
Am I use class annotation for gen-class in a wrong way? or just Clojure compiler just have this bug?
P.S. tried with Clojure 1.9, 1.10, 1.10.1

Finally, I use native Java code instead. I realized that writing classes in clojure with only ctor forwarded to super class is making trouble for myself.
I embedded java code in the project along with :java-source-paths configured in defproject, got my problem solved.

Related

Binary/Qualified name is wrong? Begins with: <any?>$

I am running an annotation processor that I have wrote. It ran fine on JDK 8 and now I am experiencing a problem on JDK 12.
I have a TypeElement and I want to retrieve its binary name to pass to Class.forName.
I use javax.lang.model.util.Elements.getBinaryName(TypeElement) and it returns a garbage value <any?>$OuterClass.InnerClass instead of the expected example3.OuterClass$InnerClass.
I attempted to replace getBinaryName with TypeElement.getQualifiedName (even though it would not quite work for an inner class) but it gives me the same garbage result. I have tried searching for this issue but most search engines strip all the special characters and give me useless results.
The TypeElement was obtained by catching a MirroredTypeException like so:
try {
exampleAnnotation.value();
throw new IllegalStateException("Expected a MirroredTypeException.");
} catch (MirroredTypeException ex) {
return (TypeElement) types.asElement(ex.getTypeMirror());
}
And here is the definition of ExampleAnnotation:
package example1;
#Target(PACKAGE)
#Retention(RUNTIME)
#Documented
public #interface ExampleAnnotation {
Class<? extends Derived> value() default Derived.class;
interface Derived<A extends Annotation> extends Base<A> {
String foo();
}
}
And here is the instance of the annotation that the processor is accessing in package-info.java:
#ExampleAnnotation(OuterClass.InnerClass.class)
package example2;
import example1.ExampleAnnotation;
I have also tried the fully qualified name example3.OuterClass.InnerClass.class but that also results in garbage: <any?>$example3.OuterClass.InnerClass.
I doubt it matters but the annotation processors are still marked #SupportedSourceVersion(SourceVersion.RELEASE_8) and I am running this on Gradle 5.3.1.
I've verified the processorpath contains the jars for packages example1 and example3, including the annotation processors.
I've made no changes to account for the module system so I was thinking maybe that's somehow affecting the code.
Just tried creating a Maven project and am currently unable to reproduce the problem, so there may be an issue with my Gradle configuration, similar to what #Colin Alworth has suggested.
I had recently upgraded to a new version of Gradle and started using the "annotationProcessor" dependencies.
It appears that <any?>$ is prepended to binary/qualified class names (as it appears in the source) if the class isn't on the classpath (or if it isn't imported, or is spelled wrong). I only had the annotation's jar on the processorpath.
To alert consumers of my annotation processor of this mistake, I was able to detect it by comparing TypeElement.asType().getKind() == TypeKind.ERROR immediately after catching the MirroredTypeException.

swagger-maven-plugin triggers Javadoc warning: element value must be a constant expression (but it is!) in Java annotation

Javadoc (via Maven) is giving me the following error in one my Java JAX-RS interface method signatures:
error: element value must be a constant expression
Here is my JAX-RS interface:
public interface FooResource {
#Consumes(APPLICATION_FORM_URLENCODED_UTF_8)
public void bar();
}
Javdoc gives the error for #Consumes. Here is the definition for APPLICATION_FORM_URLENCODED_UTF_8, which appears in MyAppConstants in the same project:
public static final String APPLICATION_FORM_URLENCODED_UTF_8 =
APPLICATION_FORM_URLENCODED + ";" + CHARSET_PARAMETER + "=UTF-8";
And here is the definition of APPLICATION_FORM_URLENCODED, which appears in javax.ws.rs.core.MediaType:
public final static String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
And here is the definition of CHARSET_PARAMETER, which also appears in javax.ws.rs.core.MediaType:
public static final String CHARSET_PARAMETER = "charset";
Now I ask you: what about APPLICATION_FORM_URLENCODED_UTF_8 is not constant at compile time?
The error message didn't say that I have to provide a literal. It said I had to provide a constant. So what about this is not a constant?
(I could almost swear that this worked at one time but suddenly stopped working.)
Update: Found cause, but still don't understand.
For some reason, merely including the swagger-maven-plugin in the POM will trigger this problem! My code doesn't change at all, but as soon as I add the following dependency, suddenly I get Javadoc warnings for my existing code!!!
<dependency>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>3.1.5</version>
</dependency>
How can a single dependency make Javadoc work differently on a code file? What is swagger-maven-plugin doing?
My best guess is that this happens because swagger-maven-plugin transitively (via io.swagger:swagger-core:1.5.13) an old version of the JAX-RS specification in javax.ws.rs:jsr311-api:1.1.1. Note that the JAX-RS 2 artifact ID is javax.ws.rs-api, Maven doesn't realize that they are different versions of the same JAR, and pulls them both in as dependencies. I can only guess that javax.ws.rs:jsr311-api in fact does not use constants for the variables in question. In any case, when I threw out swagger-maven-plugin and pulled in io.swagger:swagger-annotations (which was all I needed in this project for documentation), the problem went away.
See https://github.com/kongchen/swagger-maven-plugin/issues/543.

java.util.ServiceLoader.load() function is useless and only returns empty result

I'm trying to use Java ServiceLoader in Scala 2.10 to find all my test classes by reflection:
val services = ServiceLoader.load(classOf[MyClass])
for (service <- services.asScala) {
test(service.getClass.getCanonicalName) {
println(service)
... test code
}
}
I'm very sure 'MyClass' has several subclasses which contains test cases, as I can use 'classOf[]' function to find them in the same code snippet
However, my test always ends with
Empty test suite.
Is the ServiceLoader not working in Scala? How to fix or circumvent this problem?
Sorry I forgot to set the META-INF/services/ file, after adding the binary name of the service class everything goes normal!

MWE2 Workflow component with Scala language

I'm trying to write a MWE2 workflow component using scala language.
Below is the scala code:
package com.ford.modelling.workflow
import org.eclipse.emf.mwe2.runtime.workflow.{IWorkflowComponent, IWorkflowContext}
class SayHello extends IWorkflowComponent {
var message : String = null;
def preInvoke {}
def invoke(ctx : IWorkflowContext) { System.out.println(message) }
def postInvoke {}
}
and below is the workflow code:
module com.ford.modelling.workflow.SomeWorklow
SayHello {}
I can't figure out why does this workflow complain for error:
'com.ford.modelling.workflow.SayHello' does not have a public default constructor.
I'd assume that the scala IDE plugin does not mimic the java protocol completely, e.g. the IType does not expose a no-args constructor. You may want to ask the scale folks about it.
The error message should vanish as soon as you add a default constructor explicitly. Does that make sense?
A quick google search indicated that there probably no syntax for a default constructor so I'd assume it's a scala tooling problem. Does the problem occur at runtime, too?

AspectJ Load time weaver doesn't detect all classes

I am using Spring's declarative transactions (the #Transactional annotation) in "aspectj" mode. It works in most cases exactly like it should, but for one it doesn't. We can call it Lang (because that's what it's actually called).
I have been able to pinpoint the problem to the load time weaver. By turning on debug and verbose logging in aop.xml, it lists all classes being woven. The problematic class Lang is indeed not mentioned in the logs at all.
Then I put a breakpoint at the top of Lang, causing Eclipse to suspend the thread when the Lang class is loaded. This breakpoint is hit while the LTW weaving other classes! So I am guessing it either tries to weave Lang and fails and doesn't output that, or some other class has a reference that forces it to load Lang before it actually gets a chance to weave it.
I am unsure however how to continue to debug this, since I am not able to reproduce it in smaller scale. Any suggestions on how to go on?
Update: Other clues are also welcome. For example, how does the LTW actually work? There appears to be a lot of magic happening. Are there any options to get even more debug output from the LTW? I currently have:
<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
I forgot tom mention it before: spring-agent is being used to allow LTW, i.e., the InstrumentationLoadTimeWeaver.
Based on the suggestions of Andy Clement I decided to inspect whether the AspectJ transformer is ever even passed the class. I put a breakpoint in ClassPreProcessorAgent.transform(..), and it seems that the Lang class never even reaches that method, despite it being loaded by the same class loader as other classes (an instance of Jetty's WebAppClassLoader).
I then went on to put a breakpoint in InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Not even that one is hit for Lang. And I believe that method should be invoked for all loaded classes, regardless of what class loader they are using. This is starting to look like:
A problem with my debugging. Possibly Lang is not loaded at the time when Eclipse reports it is
Java bug? Far-fetched, but I suppose it does happen.
Next clue: I turned on -verbose:class and it appears as if Lang is being loaded prematurely - probably before the transformer is added to Instrumentation. Oddly, my Eclipse breakpoint does not catch this loading.
This means that Spring is new suspect. there appears to be some processing in ConfigurationClassPostProcessor that loads classes to inspect them. This could be related to my problem.
These lines in ConfigurationClassBeanDefinitionReader causes the Lang class to be read:
else if (metadata.isAnnotated(Component.class.getName()) ||
metadata.hasAnnotatedMethods(Bean.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
return true;
}
In particular, metadata.hasAnnotatedMethods() calls getDeclaredMethods() on the class, which loads all parameter classes of all methods in that class. I am guessing that this might not be the end of the problem though, because I think the classes are supposed to be unloaded. Could the JVM be caching the class instance for unknowable reasons?
OK, I have solved the problem. Essentially, it is a Spring problem in conjunction with some custom extensions. If anyone comes across something similar, I will try to explain step by step what is happening.
First of all, we have a custom BeanDefintionParser in our project. This class had the following definition:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class<?> getBeanClass(Element element) {
try {
return Class.forName(element.getAttribute("class"));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e);
}
}
// code to parse XML omitted for brevity
}
Now, the problem occurs after all bean definition have been read and BeanDefinitionRegistryPostProcessor begins to kick in. At this stage, a class called ConfigurationClassPostProcessor starts looking through all bean definitions, to search for bean classes annotated with #Configuration or that have methods with #Bean.
In the process of reading annotations for a bean, it uses the AnnotationMetadata interface. For most regular beans, a subclass called AnnotationMetadataVisitor is used. However, when parsing the bean definitions, if you have overriden the getBeanClass() method to return a class instance, like we had, instead a StandardAnnotationMetadata instance is used. When StandardAnnotationMetadata.hasAnnotatedMethods(..) is invoked, it calls Class.getDeclaredMethods(), which in turn causes the class loader to load all classes used as parameters in that class. Classes loaded this way are not correctly unloaded, and thus never weaved, since this happens before the AspectJ transformer registered.
Now, my problem was that I had a class like so:
public class Something {
private Lang lang;
public void setLang(Lang lang) {
this.lang = lang;
}
}
Then, I had a bean of class Something that was parsed using our custom ControllerBeanDefinitionParser. This triggered the wrong annotation detection procedure, which triggered unexpected class loading, which meant that AspectJ never got a chance to weave Lang.
The solution was to not override getBeanClass(..), but instead override getBeanClassName(..), which according to the documentation is preferable:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected String getBeanClassName(Element element) {
return element.getAttribute("class");
}
// code to parse XML omitted for brevity
}
Lesson of the day: Do not override getBeanClass unless you really mean it. Actually, don't try to write your own BeanDefinitionParser unless you know what you're doing.
Fin.
If your class is not mentioned in the -verbose/-debug output, that suggests to me it is not being loaded by the loader you think it is. Can you be 100% sure that 'Lang' isn't on the classpath of a classloader higher in the hierarchy? Which classloader is loading Lang at the point in time when you trigger your breakpoint?
Also, you don't mention AspectJ version - if you are on 1.6.7 that had issues with ltw for anything but a trivial aop.xml. You should be on 1.6.8 or 1.6.9.
How does ltw actually work?
Put simply, an AspectJ weaver is created for each classloader that may want to weave code. AspectJ is asked if it wants to modify the bytes for a class before it is defined to the VM. AspectJ looks at any aop.xml files it can 'see' (as resources) through the classloader in question and uses them to configure itself. Once configured it weaves the aspects as specified, taking into account all include/exclude clauses.
Andy Clement
AspectJ Project Lead
Option 1) Aspect J is open source. Crack it open and see what is going on.
Option 2) Rename your class to Bang, see if it starts working
I would not be surprised if there is hard coding to skip "lang' in there, though I can't say why.
Edit -
Seeing code like this in the source
if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object
superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class);
superclassname = Utility.compactClassName(superclassname, false);
} else {
superclassname = "java.lang.Object";
}
Looks like they are trying to skip weaving of java.lang.stuff.... don't see anything for just "lang" but it may be there (or a bug)

Categories