Drools- how to find out which all rules were matched? - java

I've one .DRL file which has say 10 rules. Once I insert a fact, some rules may be matched- how do I find out which rules were matched programmatically?

Note that this answer is valid for versions of Drools up to 5.x. If you have moved on to 6 or above, then take a look at the modified answer from #melchoir55. I haven't tested it myself, but I'll trust that it works.
To keep track of rule activations, you can use an AgendaEventListener. Below is an example, as found here:
https://github.com/gratiartis/sctrcd-payment-validation-web/blob/master/src/main/java/com/sctrcd/drools/util/TrackingAgendaEventListener.java
You just need to create such a listener and attach it to the session like so:
ksession = kbase.newStatefulKnowledgeSession();
AgendaEventListener agendaEventListener = new TrackingAgendaEventListener();
ksession.addEventListener(agendaEventListener);
//...
ksession.fireAllRules();
//...
List<Activation> activations = agendaEventListener.getActivationList();
Note that there is also WorkingMemoryEventListener which enables you to do the same with tracking insertions, updates and retractions of facts.
Code for a tracking & logging AgendaEventListener:
package com.sctrcd.drools.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.drools.definition.rule.Rule;
import org.drools.event.rule.DefaultAgendaEventListener;
import org.drools.event.rule.AfterActivationFiredEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A listener that will track all rule firings in a session.
*
* #author Stephen Masters
*/
public class TrackingAgendaEventListener extends DefaultAgendaEventListener {
private static Logger log = LoggerFactory.getLogger(TrackingAgendaEventListener.class);
private List<Activation> activationList = new ArrayList<Activation>();
#Override
public void afterActivationFired(AfterActivationFiredEvent event) {
Rule rule = event.getActivation().getRule();
String ruleName = rule.getName();
Map<String, Object> ruleMetaDataMap = rule.getMetaData();
activationList.add(new Activation(ruleName));
StringBuilder sb = new StringBuilder("Rule fired: " + ruleName);
if (ruleMetaDataMap.size() > 0) {
sb.append("\n With [" + ruleMetaDataMap.size() + "] meta-data:");
for (String key : ruleMetaDataMap.keySet()) {
sb.append("\n key=" + key + ", value="
+ ruleMetaDataMap.get(key));
}
}
log.debug(sb.toString());
}
public boolean isRuleFired(String ruleName) {
for (Activation a : activationList) {
if (a.getRuleName().equals(ruleName)) {
return true;
}
}
return false;
}
public void reset() {
activationList.clear();
}
public final List<Activation> getActivationList() {
return activationList;
}
public String activationsToString() {
if (activationList.size() == 0) {
return "No activations occurred.";
} else {
StringBuilder sb = new StringBuilder("Activations: ");
for (Activation activation : activationList) {
sb.append("\n rule: ").append(activation.getRuleName());
}
return sb.toString();
}
}
}

Steve's answer is solid, but the major changes brought in drools 6 make the code obsolete. I am posting below a rewrite of Steve's code which takes into account the new api:
package your.preferred.package;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.drools.core.event.DefaultAgendaEventListener;
import org.kie.api.definition.rule.Rule;
import org.kie.api.event.rule.AfterMatchFiredEvent;
import org.kie.api.runtime.rule.Match;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A listener that will track all rule firings in a session.
*
* #author Stephen Masters, Isaac Martin
*/
public class TrackingAgendaEventListener extends DefaultAgendaEventListener {
private static Logger log = LoggerFactory.getLogger(TrackingAgendaEventListener.class);
private List<Match> matchList = new ArrayList<Match>();
#Override
public void afterMatchFired(AfterMatchFiredEvent event) {
Rule rule = event.getMatch().getRule();
String ruleName = rule.getName();
Map<String, Object> ruleMetaDataMap = rule.getMetaData();
matchList.add(event.getMatch());
StringBuilder sb = new StringBuilder("Rule fired: " + ruleName);
if (ruleMetaDataMap.size() > 0) {
sb.append("\n With [" + ruleMetaDataMap.size() + "] meta-data:");
for (String key : ruleMetaDataMap.keySet()) {
sb.append("\n key=" + key + ", value="
+ ruleMetaDataMap.get(key));
}
}
log.debug(sb.toString());
}
public boolean isRuleFired(String ruleName) {
for (Match a : matchList) {
if (a.getRule().getName().equals(ruleName)) {
return true;
}
}
return false;
}
public void reset() {
matchList.clear();
}
public final List<Match> getMatchList() {
return matchList;
}
public String matchsToString() {
if (matchList.size() == 0) {
return "No matchs occurred.";
} else {
StringBuilder sb = new StringBuilder("Matchs: ");
for (Match match : matchList) {
sb.append("\n rule: ").append(match.getRule().getName());
}
return sb.toString();
}
}
}

You can use a static logger factory which will log with your favorite logger the actions from your DRL file.
For instance:
import org.drools.runtime.rule.RuleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class DRLLogger {
private DRLLogger() {
}
protected static Logger getLogger(final RuleContext drools) {
final String category = drools.getRule().getPackageName() + "." + drools.getRule().getName();
final Logger logger = LoggerFactory.getLogger(category);
return logger;
}
public static void info(final RuleContext drools, final String message, final Object... parameters) {
final Logger logger = getLogger(drools);
logger.info(message, parameters);
}
public static void debug(final RuleContext drools, final String message, final Object... parameters) {
final Logger logger = getLogger(drools);
logger.debug(message, parameters);
}
public static void error(final RuleContext drools, final String message, final Object... parameters) {
final Logger logger = getLogger(drools);
logger.error(message, parameters);
}
}
Then from your DRL file:
import function com.mycompany.DRLLogger.*
rule "myrule"
when
$fact: Fact()
then
info(drools, "Fact:{}", $fact);
end

Change the dialect to JAVA in DRL file.
Insert a HashMap from the java file to DRL file (using Drools session concept),
which should contain the rule name as key and boolean value as result.
Follow this link to know how to insert Map to the DRL file.
You can now find which rule exactly matched.
Hope this helps :)

You can print info about rule executed from DRL file itself using RuleContext:drools
System.out.println(drools.getRule().getName())

Related

Prometheus requires that all meters with the same name have the same set of tag keys

If #Around only #Timed annotated method like this:
package ru.fabit.visor.config.aop;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.lang.NonNullApi;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.scheduling.annotation.Scheduled;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* The type Targeted timed aspect.
*/
#Aspect
#NonNullApi
public class TargetedTimedAspect {
public static final String DEFAULT_METRIC_NAME = "method.timed";
public static final String EXCEPTION_TAG = "exception";
public static final String BINDING_TAG = "binding";
public static final String SCHEDULED_CRON_TAG = "cron";
private final MeterRegistry registry;
private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;
public TargetedTimedAspect(MeterRegistry registry) {
this(registry, pjp ->
Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
"method", pjp.getStaticPart().getSignature().getName())
);
}
public TargetedTimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
this.registry = registry;
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
}
// enable TimedAspect only for #StreamListener and #Scheduled annotated methods or allowed methods pointcut
#Around("timedAnnotatedPointcut() )")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
StreamListener streamListener = method.getAnnotation(StreamListener.class);
Scheduled scheduled = method.getAnnotation(Scheduled.class);
// timed can be on method or class
Timed timed = method.getAnnotation(Timed.class);
if (timed == null) {
method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
timed = method.getAnnotation(Timed.class);
}
final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();
Timer.Sample sample = Timer.start(registry);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
Timer.Builder timerBuilder = Timer.builder(metricName)
.description(timed.description().isEmpty() ? null : timed.description())
.tags(timed.extraTags())
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());
if (streamListener != null) {
timerBuilder.tags(
BINDING_TAG,
streamListener.value().isEmpty() ? streamListener.target() : streamListener.value()
);
} else if (scheduled != null) {
timerBuilder.tags(SCHEDULED_CRON_TAG, scheduled.cron());
}
sample.stop(timerBuilder.register(registry));
} catch (Exception e) {
// ignoring on purpose
}
}
}
#Pointcut(
"(#annotation(org.springframework.cloud.stream.annotation.StreamListener) ||" +
"#annotation(org.springframework.scheduling.annotation.Scheduled))"
)
public void asyncAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("execution(public * ru.fabit.visor.service.impl.StorageClientImpl.*(..)) ||" +
"execution(public * ru.fabit.visor.service.s3storage.S3StorageClientImpl.*(..))")
public void allowedMethodPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("#annotation(io.micrometer.core.annotation.Timed)")
public void timedAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
}
Then return: java.lang.IllegalArgumentException: Prometheus requires that all meters with the same name have the same set of tag keys. There is already an existing meter named 'web_photos_gotten_list_seconds' containing tag keys [class, exception, method]. The meter you are attempting to register has keys [exception, method, outcome, status, uri].
But, if add all #Timed method in Pointcut, all good work, i dont understand, why we need all annotated method add to Pointcut separately?
This work:
package ru.fabit.visor.config.aop;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.lang.NonNullApi;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.scheduling.annotation.Scheduled;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* The type Targeted timed aspect.
*/
#Aspect
#NonNullApi
public class TargetedTimedAspect {
public static final String DEFAULT_METRIC_NAME = "method.timed";
public static final String EXCEPTION_TAG = "exception";
public static final String BINDING_TAG = "binding";
public static final String SCHEDULED_CRON_TAG = "cron";
private final MeterRegistry registry;
private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;
public TargetedTimedAspect(MeterRegistry registry) {
this(registry, pjp ->
Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
"method", pjp.getStaticPart().getSignature().getName())
);
}
public TargetedTimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
this.registry = registry;
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
}
// enable TimedAspect only for #StreamListener and #Scheduled annotated methods or allowed methods pointcut
#Around("timedAnnotatedPointcut() && (asyncAnnotatedPointcut() || allowedMethodPointcut())")
public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
StreamListener streamListener = method.getAnnotation(StreamListener.class);
Scheduled scheduled = method.getAnnotation(Scheduled.class);
// timed can be on method or class
Timed timed = method.getAnnotation(Timed.class);
if (timed == null) {
method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
timed = method.getAnnotation(Timed.class);
}
final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();
Timer.Sample sample = Timer.start(registry);
String exceptionClass = "none";
try {
return pjp.proceed();
} catch (Exception ex) {
exceptionClass = ex.getClass().getSimpleName();
throw ex;
} finally {
try {
Timer.Builder timerBuilder = Timer.builder(metricName)
.description(timed.description().isEmpty() ? null : timed.description())
.tags(timed.extraTags())
.tags(EXCEPTION_TAG, exceptionClass)
.tags(tagsBasedOnJoinPoint.apply(pjp))
.publishPercentileHistogram(timed.histogram())
.publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());
if (streamListener != null) {
timerBuilder.tags(
BINDING_TAG,
streamListener.value().isEmpty() ? streamListener.target() : streamListener.value()
);
} else if (scheduled != null) {
timerBuilder.tags(SCHEDULED_CRON_TAG, scheduled.cron());
}
sample.stop(timerBuilder.register(registry));
} catch (Exception e) {
// ignoring on purpose
}
}
}
#Pointcut(
"(#annotation(org.springframework.cloud.stream.annotation.StreamListener) ||" +
"#annotation(org.springframework.scheduling.annotation.Scheduled))"
)
public void asyncAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("execution(public * ru.fabit.visor.service.impl.StorageClientImpl.*(..)) ||" +
"execution(public * ru.fabit.visor.service.s3storage.S3StorageClientImpl.*(..))")
public void allowedMethodPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
#Pointcut("#annotation(io.micrometer.core.annotation.Timed)")
public void timedAnnotatedPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
}
pom.xml:
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
The problem youre discribing has nothing to do with pointcuts. There is one piece of code that generates a Timer with three tags (class, exception, method) and another one creating the timer with the exact same same with 5 tags (exception, method, outcome, status, uri) and the framework clearly says, that this is now allowed.
There are several possibilites to solve the issue:
simply use another name for the timer (if you need the other one)
find the other piece of code that generates that timer and deactivate it. Maybe you need to use the debugger and set an conditional breakpoint in MeterRegistry.register()` that registers the meters with the condition that the meter name matches.
PS: using the URI as a tag is not a good practice. The issue is that anyone access your service using different URIs (e.g. by just adding a random number) that will end up in a very high number of meters, which will finally kill your prometheus.

How to add "text/plain" MIME type to DataHandler

I have been struggling with getting this test to work for awhile, the relevant code executes fine in production my assumption is that it has some additional configuration, lots of searching seems to be related specifically to email handling and additional libraries, I don't want to include anything else, what am I missing to link DataHandler to a relevant way of handling "text/plain" ?
Expected result: DataHandler allows me to stream the input "Value" back into a result.
Reproduce issue with this code:
import java.io.IOException;
import java.io.InputStream;
import javax.activation.CommandInfo;
import javax.activation.CommandMap;
import javax.activation.DataHandler;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class DataHandlerTest {
#Before
public void setUp() throws Exception {
}
#After
public void tearDown() throws Exception {
}
#Test
public void test() throws IOException {
printDefaultCommandMap();
DataHandler dh = new DataHandler("Value", "text/plain");
System.out.println("DataHandler commands:");
printDataHandlerCommands(dh);
dh.setCommandMap(CommandMap.getDefaultCommandMap());
System.out.println("DataHandler commands:");
printDataHandlerCommands(dh);
final InputStream in = dh.getInputStream();
String result = new String(IOUtils.toByteArray(in));
System.out.println("Returned String: " + result);
}
private void printDataHandlerCommands(DataHandler dh) {
CommandInfo[] infos = dh.getAllCommands();
printCommands(infos);
}
private void printDefaultCommandMap() {
CommandMap currentMap = CommandMap.getDefaultCommandMap();
String[] mimeTypes = currentMap.getMimeTypes();
System.out.println("Found " + mimeTypes.length + " MIME types.");
for (String mimeType : mimeTypes) {
System.out.println("Commands for: " + mimeType);
printCommands(currentMap.getAllCommands(mimeType));
}
}
private void printCommands(CommandInfo[] infos) {
for (CommandInfo info : infos) {
System.out.println(" Command Class: " +info.getCommandClass());
System.out.println(" Command Name: " + info.getCommandName());
}
}
}
Exception:
javax.activation.UnsupportedDataTypeException: no object DCH for MIME
type text/plain at
javax.activation.DataHandler.getInputStream(DataHandler.java:249)
Help much appreciated, I hope this is a well formed question!
========================
Update 25th February
I have found if i know I stored a String in DataHandler, then I can cast the result to String and return the object that was stored, example:
#Test
public void testGetWithoutStream() throws IOException {
String inputString = "Value";
DataHandler dh = new DataHandler(inputString, "text/plain");
String rawResult = (String) dh.getContent();
assertEquals(inputString, rawResult);
}
But the code under test uses an InputStream, so my 'real' tests still fail when executed locally.
Continuing my investigation and still hoping for someone's assistance/guidance on this one...
Answering my own question for anyone's future reference.
All credit goes to: https://community.oracle.com/thread/1675030?start=0
The principle here is that you need to provide DataHandler a factory that contains a DataContentHandler that will behave as you would like it to for your MIME type, setting this is via a static method that seems to affect all DataHandler instances.
I declared a new class (SystemDataHandlerConfigurator), which has a single public method that creates my factory and provides it the static DataHandler.setDataContentHandlerFactory() function.
My tests now work correctly if I do this before they run:
SystemDataHandlerConfigurator configurator = new SystemDataHandlerConfigurator();
configurator.setupCustomDataContentHandlers();
SystemDataHandlerConfigurator
import java.io.IOException;
import javax.activation.*;
public class SystemDataHandlerConfigurator {
public void setupCustomDataContentHandlers() {
DataHandler.setDataContentHandlerFactory(new CustomDCHFactory());
}
private class CustomDCHFactory implements DataContentHandlerFactory {
#Override
public DataContentHandler createDataContentHandler(String mimeType) {
return new BinaryDataHandler();
}
}
private class BinaryDataHandler implements DataContentHandler {
/** Creates a new instance of BinaryDataHandler */
public BinaryDataHandler() {
}
/** This is the key, it just returns the data uninterpreted. */
public Object getContent(javax.activation.DataSource dataSource) throws java.io.IOException {
return dataSource.getInputStream();
}
public Object getTransferData(java.awt.datatransfer.DataFlavor dataFlavor,
javax.activation.DataSource dataSource)
throws java.awt.datatransfer.UnsupportedFlavorException,
java.io.IOException {
return null;
}
public java.awt.datatransfer.DataFlavor[] getTransferDataFlavors() {
return new java.awt.datatransfer.DataFlavor[0];
}
public void writeTo(Object obj, String mimeType, java.io.OutputStream outputStream)
throws java.io.IOException {
if (mimeType == "text/plain") {
byte[] stringByte = (byte[]) ((String) obj).getBytes("UTF-8");
outputStream.write(stringByte);
}
else {
throw new IOException("Unsupported Data Type: " + mimeType);
}
}
}
}

Introspecting Jersey resource model Jersey 2.x

I have written my own scanner to go through my JAX-RS resources and print out the method names and paths using jersey-server-1.18.1. The problem is when I migrate my same code to 2.16 (changing the package names from com.sun.* to org.glassfish.*), It just won't work.
Digging deep I found that those required jersey-server classes are no long public. Anyone knows the reason why? And how can I migrate my code below from 1.x to 2.x ? There is literally no documentation on this migration.
All help appreciated! Below is the code with 1.x
import com.wordnik.swagger.annotations.ApiOperation;
import com.sun.jersey.api.model.AbstractResource;
import com.sun.jersey.api.model.AbstractResourceMethod;
import com.sun.jersey.api.model.AbstractSubResourceLocator;
import com.sun.jersey.api.model.AbstractSubResourceMethod;
import com.sun.jersey.server.impl.modelapi.annotation.IntrospectionModeller;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author shivang
*/
public class Apiscanner {
public static void main(String[] args) {
Apiscanner runClass = new Apiscanner();
runClass.xyz();
}
public void xyz() {
AbstractResource resource = IntrospectionModeller.createResource(BaseResource.class);
String uriPrefix = resource.getPath().getValue();
abc(uriPrefix, resource);
}
public void abc(String uriPrefix, AbstractResource resource) {
for (AbstractResourceMethod srm : resource.getResourceMethods()) {
String uri = uriPrefix;
System.out.println(srm.getHttpMethod() + "\t" + uri);
}
for (AbstractSubResourceMethod srm : resource.getSubResourceMethods()) {
String uri = uriPrefix + srm.getPath().getValue();
ApiOperation op = srm.getAnnotation(ApiOperation.class);
System.out.println(srm.getHttpMethod() + "\t" + uri);
}
if (resource.getSubResourceLocators() != null && !resource.getSubResourceLocators().isEmpty()) {
for (AbstractSubResourceLocator subResourceLocator : resource.getSubResourceLocators()) {
ApiOperation op = subResourceLocator.getAnnotation(ApiOperation.class);
AbstractResource childResource = IntrospectionModeller.createResource(op.response());
String path = subResourceLocator.getPath().getValue();
String pathPrefix = uriPrefix + path;
abc(pathPrefix, childResource);
}
}
}
}
The new APIs for Jersey 2.x, can mainly be found in the org.glassfish.jersey.server.model package.
Some equivalents I can think of:
AbstractResource == Resource
IntrospectionModeller.createResource == I believe Resource.from(BaseResource.class)
AbstractResourceMethod == ResourceMethod
resource.getSubResourceMethods() == getChildResources(), which actually just returns a List<Resource>
AbstractSubResourceLocator == Doesn't seem to exist. We would simply check the above child resource to see if it is a locator
for (Resource childResource: resource.getChildResources()) {
if (childResource.getResourceLocator() != null) {
ResourceMethod method = childResource.getResourceLocator();
Class locatorType = method.getInvocable().getRawResponseType();
}
}
You can also use the enum ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR to check if it equals the ResourceMethod.getType()
if (resourceMethod.getType()
.equals(ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR) {
}
Here's what I was able to come up with, to kind of match what you got.
import com.wordnik.swagger.annotations.ApiOperation;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
public class ApiScanner {
public static void main(String[] args) {
ApiScanner scanner = new ApiScanner();
scanner.xyz();
}
public void xyz() {
Resource resource = Resource.from(BaseResource.class);
abc(resource.getPath(), resource);
}
public void abc(String uriPrefix, Resource resource) {
for (ResourceMethod resourceMethod: resource.getResourceMethods()) {
String uri = uriPrefix;
System.out.println("-- Resource Method --");
System.out.println(resourceMethod.getHttpMethod() + "\t" + uri);
ApiOperation api = resourceMethod.getInvocable().getDefinitionMethod()
.getAnnotation(ApiOperation.class);
}
for (Resource childResource: resource.getChildResources()) {
System.out.println("-- Child Resource --");
System.out.println(childResource.getPath() + "\t" + childResource.getName());
if (childResource.getResourceLocator() != null) {
System.out.println("-- Sub-Resource Locator --");
ResourceMethod method = childResource.getResourceLocator();
Class locatorType = method.getInvocable().getRawResponseType();
System.out.println(locatorType);
Resource subResource = Resource.from(locatorType);
abc(childResource.getPath(), subResource);
}
}
}
}
OK. So I was able to get it to work almost at the same time as #peeskillet provided the answer. I will add just a different flavor of the answer in case people want to reuse the code:
import java.util.ArrayList;
import java.util.List;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author shivang
*/
public class JerseyResourceScanner {
public static void main(String[] args) {
JerseyResourceScanner runClass = new JerseyResourceScanner();
runClass.scan(BaseResource.class);
}
public void scan(Class baseClass) {
Resource resource = Resource.builder(baseClass).build();
String uriPrefix = "";
process(uriPrefix, resource);
}
private void process(String uriPrefix, Resource resource) {
String pathPrefix = uriPrefix;
List<Resource> resources = new ArrayList<>();
resources.addAll(resource.getChildResources());
if (resource.getPath() != null) {
pathPrefix = pathPrefix + resource.getPath();
}
for (ResourceMethod method : resource.getAllMethods()) {
if (method.getType().equals(ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR)) {
resources.add(
Resource.from(resource.getResourceLocator()
.getInvocable().getDefinitionMethod().getReturnType()));
}
else {
System.out.println(method.getHttpMethod() + "\t" + pathPrefix);
}
}
for (Resource childResource : resources) {
process(pathPrefix, childResource);
}
}
}

How to retrieve a list of included client libs from a component in CQ?

Is it possible to determine, what client libs have been loaded prior to a component?
We are running multiple site backed by different Javascript frameworks. In order to run a single component across the board, it's not sufficient to just use
<cq:includeClientLib categories="blah"/>
We need to identify the respective framework (i.e. AngularJS, Vanilla, jQuery, blah) in order to facilitate the integration.
We are looking for a decent server side solution.
I haven't actually done this, but it would presumably be possible if you are buffering your output to clone the JspWriter buffer or examine it to see what it already contains. That sounds ugly to me, though. But this is decompiled code for how the cq:includeClientLib tag adds libraries to the output, which may show you how you can read back what was previously written:
package com.adobe.granite.ui.tags;
import com.day.cq.widget.HtmlLibraryManager;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.scripting.jsp.util.TagUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IncludeClientLibraryTag extends TagSupport {
private static final long serialVersionUID = -3068291967085012331L;
private static final Logger log = LoggerFactory.getLogger(IncludeClientLibraryTag.class);
private String categories;
private String js;
private String css;
private String theme;
private Boolean themed;
public IncludeClientLibraryTag() {
}
public void setPageContext(PageContext pageContext) {
super.setPageContext(pageContext);
this.categories = null;
this.js = null;
this.css = null;
this.theme = null;
this.themed = null;
}
public void setCategories(String categories) {
this.categories = categories;
}
public void setJs(String js) {
this.js = js;
}
public void setCss(String css) {
this.css = css;
}
public void setTheme(String theme) {
this.theme = theme;
}
public void setThemed(boolean themed) {
this.themed = Boolean.valueOf(themed);
}
public int doEndTag() throws JspException {
SlingHttpServletRequest request = TagUtil.getRequest(this.pageContext);
HtmlLibraryManager libManager = this.getHtmlLibraryManager(request);
if(libManager == null) {
log.warn("<ui:includeClientLib>: Could not retrieve HtmlLibraryManager service, skipping inclusion.");
return 6;
} else {
JspWriter out = this.pageContext.getOut();
try {
if(this.categories != null) {
libManager.writeIncludes(request, out, toArray(this.categories));
} else if(this.theme != null) {
libManager.writeThemeInclude(request, out, toArray(this.theme));
} else if(this.js != null) {
if(this.themed != null) {
libManager.writeJsInclude(request, out, this.themed.booleanValue(), toArray(this.js));
} else {
libManager.writeJsInclude(request, out, toArray(this.js));
}
} else if(this.css != null) {
if(this.themed != null) {
libManager.writeCssInclude(request, out, this.themed.booleanValue(), toArray(this.css));
} else {
libManager.writeCssInclude(request, out, toArray(this.css));
}
}
return 6;
} catch (IOException var6) {
String libs = this.categories != null?"categories: " + this.categories:(this.theme != null?"theme: " + this.theme:(this.js != null?"js: " + this.js:(this.css != null?"css: " + this.css:"")));
throw new JspException("Could not include client library: " + libs, var6);
}
}
}
private HtmlLibraryManager getHtmlLibraryManager(ServletRequest request) {
SlingBindings bindings = (SlingBindings)request.getAttribute(SlingBindings.class.getName());
return (HtmlLibraryManager)bindings.getSling().getService(HtmlLibraryManager.class);
}
private static String[] toArray(String commaSeparatedList) {
if(commaSeparatedList == null) {
return new String[0];
} else {
String[] split = commaSeparatedList.split(",");
for(int i = 0; i < split.length; ++i) {
split[i] = split[i].trim();
}
return split;
}
}
}
I think the best solution may be to use the client library dependencies or embed attributes in your library, though, or let the client-side JavaScript test if a library is present (ex. test if the jQuery object is undefined) and then take appropriate action. In other words, let the client side determine the final rendering based on what libraries exist on in the client. It sounds like this may not be possible for your situation, though.
dependencies: This is a list of other client library categories on
which this library folder depends. For example, given two
cq:ClientLibraryFolder nodes F and G, if a file in F requires another
file in G in order to function properly, then at least one of the
categories of G should be among the dependencies of F.
embed: Used to > embed code from other libraries. If node F embeds nodes G and H, the
resulting HTML will be a concetration of content from nodes G and H.

Annotation processing for adding message attribute to existing JSR-303 annotations on fields

I have a data transfer object that's annotated with JSR-303 constraints like...
public class AssetOwnedDailyLocatableId implements Serializable, AssetOwned, HasOperatingDay, Locatable {
private static final long serialVersionUID = 1L;
#NotEmpty
#Size(min = 1, max = 30)
private String locationName;
#NotEmpty
private String operatingDay;
#NotEmpty
#Size(min = 1, max = 30)
private String assetOwner;
I am attempting to use Annotation Processing to enrich each JSR-303 constraint with a message attribute whose value would be equal to the constraint-name.class-name.member-name.
E.g., using the above, the final generated output for the locationName field's annotations would look like...
#NotEmpty(message="{NotEmpty.AssetOwnedDailyLocatableId.locationName}")
#Size(min = 1, max = 30, message="{Size.AssetOwnedDailyLocatableId.locationName}")
private String locationName;
Why? Because I want complete control over custom validation messaging. I have well over hundreds of data transfer objects that I would like to process with something like...
/**
* ViolationConstraint message processor. During compile time it scans all DTO
* classes that have <code>javax.validation.constrants.*</code> or
* <code>org.hibernate.validator.constraints.*</code>annotated
* fields, then enriches the annotation with a <code>message</code> attribute
* where its value will be <code>constraint-name.class-name.field-name</code>.
*
* #param <T>
* any JSR-303 annotation type
*
*/
#SupportedSourceVersion(SourceVersion.RELEASE_6)
#SupportedAnnotationTypes(value = { "javax.validation.constraints.*", "org.hibernate.validator.constraints.*" })
public class ValidationMessagesProcessor<T extends Annotation> extends AbstractProcessor {
private static final String JAVAX_PATH = "javax.validation.constraints.*";
private static final String HIBERNATE_PATH = "org.hibernate.validator.constraints/*";
private PackageUtil<T> util;
public ValidationMessagesProcessor() {
super();
util = new PackageUtil<T>();
}
/* (non-Javadoc)
* #see javax.annotation.processing.AbstractProcessor#process(java.util.Set, javax.annotation.processing.RoundEnvironment)
*/
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
if (!roundEnvironment.processingOver()) {
String message;
message = ValidationMessagesProcessor.class.getName() + " will begin processing now...";
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
try {
final List<Class<T>> annotationTypes = new ArrayList<Class<T>>();
final List<Class<T>> jxTypes = util.listMatchingClasses(JAVAX_PATH);
final List<Class<T>> hibTypes = util.listMatchingClasses(HIBERNATE_PATH);
annotationTypes.addAll(jxTypes);
annotationTypes.addAll(hibTypes);
for (final Element e : roundEnvironment.getRootElements()) {
// TODO Do the real work!
/*message = "... JSR-303 annotation '" + a.annotationType().getClass().getName() + "' found in "
+ e.getSimpleName();
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message); */
}
} catch (final IOException ioe) {
message = "Failed to locate javax.validation.constraints or org.hibernate.validator.constraints classes on classpath!";
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
}
}
return true; // no further processing of this annotation type
}
}
I want to know if the above approach is feasible, or if I should try something else (that might be simpler). Furthermore, if it is feasible, some direction on what to implement within the //TODO section of the processor above. So far I've consulted...
http://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html#resources
http://blog.xebia.com/2009/07/21/testing-annotation-processors/
The drawbacks of annotation processing in Java?
Writing an annotation processor for maven-processor-plugin
How to use custom annotation processor with Maven 2?
Can I get from a TypeVariable or VariableElement to a list of Methods on the underlying class In an annotation processor at compile time
So I opted for authoring a utility based on Eclipse JDT.
Took me a while to hunt down all the dependent libs to make this work. For anyone else interested here's the Maven dependencies:
<!-- Validation API and Impl -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<!-- Hibernate validator impl -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- Required to power classpath scanning for JSR-303 classes within JAR packages -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<!-- Required to employ all Eclipse JDT capabilities -->
<!-- This specific collection of artifact versions is known to work together -->
<!-- Take caution when upgrading versions! -->
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.8.1.v20120502-0834</version>
</dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.8.0.v20120430-1750</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.resources</artifactId>
<version>3.7.100.v20110510-0712</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.7.0.v_B61</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.runtime</artifactId>
<version>3.7.0.v20110110</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.equinox.common</artifactId>
<version>3.6.0.v20110523</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.text</artifactId>
<version>3.5.100.v20110505-0800</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.jobs</artifactId>
<version>3.5.100.v20110404</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.contenttype</artifactId>
<version>3.4.100.v20110423-0524</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.equinox.preferences</artifactId>
<version>3.4.0.v20110502</version>
</dependency>
I authored four classes one with main harness and the others a facade and utils.
The harness:
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spp.im.mui.commons.jdt.JDTFacade;
import org.spp.im.mui.commons.util.FileUtil;
import org.spp.im.mui.commons.util.PackageUtil;
import org.springframework.util.CollectionUtils;
/**
* A utility that scans all DTO classes that have
* <code>javax.validation.constrants.*</code> or
* <code>org.hibernate.validation.constraints.*</code> annotated fields, then
* enriches the annotation with a <code>message</code> attribute where its value
* will be <code>constraint-name.class-name.field-name</code>.
*
* #author cphillipson
* #param <T>
* any JSR-303 annotation type
*
*/
public class ConstraintMessageUtil<T extends Annotation> {
private static Logger log = LoggerFactory.getLogger(ConstraintMessageUtil.class);
private static final String JAVAX_PATH = "/javax/validation/constraints/*";
private static final String HIBERNATE_PATH = "/org/hibernate/validator/constraints/*";
private PackageUtil<T> util;
private JDTFacade<T> facade;
public ConstraintMessageUtil() {
util = new PackageUtil<T>();
facade = new JDTFacade<T>();
}
public void process(String sourcePath) throws Exception {
// step #1: build a set of JSR-303 constraint classes
final Set<Class<T>> annotationTypes = new HashSet<Class<T>>();
try {
final List<Class<T>> jxTypes = util.listMatchingClasses(JAVAX_PATH);
final List<Class<T>> hibTypes = util.listMatchingClasses(HIBERNATE_PATH);
annotationTypes.addAll(jxTypes);
annotationTypes.addAll(hibTypes);
// remove #Valid from the mix
annotationTypes.remove(Valid.class);
Assert.isTrue(!annotationTypes.contains(Valid.class));
} catch (final IOException ioe) {
}
// step #2: get all files recursively from source path
final Collection<File> allJavaSourceInDirectory = FileUtil.getAllJavaSourceInDirectory(new File(sourcePath),
true);
// step #3: filter files to just the ones that contain annotations
final List<File> annotatedSources = new ArrayList<File>();
if (!CollectionUtils.isEmpty(allJavaSourceInDirectory)) {
boolean containsJsr303Annotation;
String typeName;
for (final File f : allJavaSourceInDirectory) {
for (final Class<T> annotationType : annotationTypes) {
typeName = annotationType.getName();
containsJsr303Annotation = FileUtil.isContentInFile(f, typeName);
if (containsJsr303Annotation) {
annotatedSources.add(f);
break; // at least one annotation found, move along
}
}
}
}
// step #4: for each annotated source file parse and rewrite with
// enriched message for each JSR-303 annotation
enrichJavaSourceFilesWithMessageAttributesForConstraintTypeAnnotatedFields(annotatedSources, annotationTypes);
}
// note: probably could have implemented an ASTVisitor, but...
protected void enrichJavaSourceFilesWithMessageAttributesForConstraintTypeAnnotatedFields(
List<File> annotatedSources, Set<Class<T>> constraintTypes) throws IOException, MalformedTreeException,
BadLocationException {
if (!CollectionUtils.isEmpty(annotatedSources)) {
// reusable local variables... a veritable cornucopia
Set<FieldDeclaration> fieldCandidates;
Document document;
String contents;
String constraintName;
String className;
String fieldName;
StringBuilder sb;
AbstractTypeDeclaration td;
IExtendedModifier[] modifiers;
CompilationUnit unit;
AST ast;
MemberValuePair mvp;
Expression exp;
NormalAnnotation na;
// iterate over all java source containing jsr-303 annotated fields
for (final File source : annotatedSources) {
unit = facade.generateCompilationUnitForFile(source);
ast = unit.getAST();
// get the set of fields which are annotated
fieldCandidates = facade.obtainAnnotatedFieldsFromClassInCompilationUnit(unit, constraintTypes);
log.info(source.getName() + " contains " + fieldCandidates.size()
+ " fields with constraint annotations.");
// iterate over each annotated field
for (final FieldDeclaration fd : fieldCandidates) {
modifiers = (IExtendedModifier[]) fd.modifiers().toArray(
new IExtendedModifier[fd.modifiers().size()]);
int i = 0;
// iterate over modifiers for the field
for (final IExtendedModifier modifier : modifiers) {
// interested in Eclipse JDT's DOM form of Annotation
if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) {
// construct the key-value pair
sb = new StringBuilder();
constraintName = ((org.eclipse.jdt.core.dom.Annotation) modifier).getTypeName().toString();
// Ignore #Valid annotations
if (!constraintName.equals(Valid.class.getSimpleName())) {
td = (AbstractTypeDeclaration) fd.getParent();
className = td.getName().toString();
fieldName = fd.fragments().get(0).toString();
// field may have an assignment, so strip it
if (fieldName.contains("=")) {
final int end = fieldName.indexOf("=");
fieldName = fieldName.substring(0, end).trim();
}
sb.append("{");
sb.append(constraintName);
sb.append(".");
sb.append(className);
sb.append(".");
sb.append(fieldName);
sb.append("}");
// construct new properties, and instead of
// updating
// the existing annotation, replace it
mvp = ast.newMemberValuePair();
mvp.setName(ast.newSimpleName("message"));
exp = ast.newStringLiteral();
((StringLiteral) exp).setLiteralValue(sb.toString());
mvp.setValue(exp);
na = ast.newNormalAnnotation();
na.setTypeName(ast.newSimpleName(constraintName));
na.values().add(mvp);
// don't forget to add the original annotation's
// member-value pairs to the new annotation
if (modifier instanceof NormalAnnotation) {
final NormalAnnotation ona = (NormalAnnotation) modifier;
final List<?> values = ona.values();
for (int j = 0; j < values.size(); j++) {
final MemberValuePair omvp = (MemberValuePair) values.get(j);
mvp = ast.newMemberValuePair();
mvp.setName(ast.newSimpleName(omvp.getName().toString()));
// a value can be a String, Number or
// reference to a constant
switch (omvp.getValue().getNodeType()) {
case ASTNode.NUMBER_LITERAL:
mvp.setValue(ast.newNumberLiteral(omvp.getValue().toString()));
break;
case ASTNode.STRING_LITERAL:
exp = ast.newStringLiteral();
((StringLiteral) exp).setLiteralValue(omvp.getValue().toString());
mvp.setValue(exp);
break;
case ASTNode.QUALIFIED_NAME:
final QualifiedName oqn = (QualifiedName) omvp.getValue();
exp = ast.newQualifiedName(ast.newName(oqn.getQualifier().toString()),
ast.newSimpleName(oqn.getName().toString()));
mvp.setValue(exp);
break;
}
na.values().add(mvp);
}
}
fd.modifiers().remove(i);
fd.modifiers().add(i, na);
log.info("#" + constraintName + " on " + fieldName + " in " + className
+ " has been enriched with a 'message' attribute whose value is now '"
+ sb.toString() + "'.");
}
i++;
}
}
}
contents = FileUtil.toString(source);
document = new Document(contents);
facade.saveUpdatesToFile(unit, document, source);
}
}
}
public static void main(String args[]) {
final ConstraintMessageUtil util = new ConstraintMessageUtil();
try {
// e.g., on Windows,
// "D:\\workspaces\\alstom-grid\\SPP-MUI\\spp-im-mui-dto\\src\\main\\java\\org\\spp\\im\\mui\\dto"
util.process(args[0]);
} catch (final Exception e) {
e.printStackTrace();
}
}
}
The utils:
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.AntPathMatcher;
/**
* Package utility. Provides handy methods for finding classes (of a particular
* type) within a package on the classpath.
*
* #author cphillipson
*
* #param <T>
* types of classes to be found in package
*/
class PackageUtil<T> {
public List<Class<T>> listMatchingClasses(String matchPattern) throws IOException {
final List<Class<T>> classes = new LinkedList<Class<T>>();
final PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
scanner.setPathMatcher(new AntPathMatcher());
final Resource[] resources = scanner.getResources("classpath:" + matchPattern);
for (final Resource resource : resources) {
final Class<T> clazz = getClassFromResource(resource);
classes.add(clazz);
}
return classes;
}
public Class<T> getClassFromResource(Resource resource) {
Class<T> result = null;
try {
String resourceUri = resource.getURI().toString();
resourceUri = resourceUri.substring(0, resourceUri.indexOf(".class")).replace("/", ".");
if (resourceUri.contains("!")) { // class was found in an archive
resourceUri = resourceUri.substring(resourceUri.indexOf("!") + 2);
}
// try printing the resourceUri before calling forName, to see if it
// is OK.
result = (Class<T>) Class.forName(resourceUri);
} catch (final Exception ex) {
ex.printStackTrace();
}
return result;
}
}
/**
* A collection of special-purposed methods for working with files and
* directories. Wraps Apache Commons I/O.
*
* #author cphillipson
*
*/
public class FileUtil {
public static Collection<File> getAllJavaSourceInDirectory(File directory, boolean recursive) {
// scans directory (and sub-directories if recursive flag is true) for
// .java files, returns a collection of files
return FileUtils.listFiles(directory, new String[] { "java" }, recursive);
}
public static boolean isContentInFile(File file, String fragment) throws IOException {
boolean result = false;
final String contents = toString(file);
if (contents.contains(fragment)) { // does file contain fragment?
result = true;
}
return result;
}
public static String toString(File file) throws IOException {
final String result = FileUtils.readFileToString(file, "utf8");
return result;
}
public static void toFile(File file, String content) throws IOException {
FileUtils.writeStringToFile(file, content, "utf8");
}
}
The facade:
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spp.im.mui.commons.util.FileUtil;
/**
* Abstract syntax tree facade. Backed by Eclipse JDT, this facade provides a
* number of conveniences, like the ability to:
* <ul>
* <li>generate an {#link CompilationUnit} from a source {#File}</li>
* <li>save updates in a {#link Document} managed by {#link CompilationUnit} to
* a {#link File}</li>
* </ul>
* and much more. Credit goes to <a href=
* "http://svn.apache.org/repos/asf/openejb/branches/eclipse-plugins-1.0.0.alpha/plugins/org.apache.openejb.devtools.core/src/main/java/org/apache/openejb/devtools/core/JDTFacade.java"
* >Apache OpenEJB DevTools JDTFacade source</a> for providing much of the
* inspiration for this implementation.
*
* #author cphillipson
* #param <T>
* any annotation type
*
*/
public class JDTFacade<T extends java.lang.annotation.Annotation> {
private static Logger log = LoggerFactory.getLogger(JDTFacade.class);
public CompilationUnit generateCompilationUnitForFile(File file) throws IOException {
final String source = FileUtil.toString(file);
final Document document = new Document(source);
final ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setSource(document.get().toCharArray());
final CompilationUnit unit = (CompilationUnit) parser.createAST(null /* no ProgressMonitor */);
unit.recordModifications();
return unit;
}
public void saveUpdatesToFile(CompilationUnit unit, Document document, File file) throws MalformedTreeException,
IOException, BadLocationException {
final TextEdit edits = unit.rewrite(document, null /* no options */);
edits.apply(document);
boolean writeable = true; // should always be able to write to file...
if (!file.canWrite()) { // .. but just in case we cannot...
writeable = file.setWritable(true);
}
if (writeable) {
FileUtil.toFile(file, document.get());
log.info("Successfully wrote updates to " + file.getName());
} else {
log.warn("Unable to write to " + file.getName());
}
}
public Set<FieldDeclaration> obtainAnnotatedFieldsFromClassInCompilationUnit(CompilationUnit unit,
Set<Class<T>> annotationTypes) {
final Set<FieldDeclaration> fields = new HashSet<FieldDeclaration>();
final List<AbstractTypeDeclaration> types = unit.types();
IExtendedModifier[] modifiers;
for (final AbstractTypeDeclaration type : types) {
if (type.getNodeType() == ASTNode.TYPE_DECLARATION) {
// Class def found
final List<BodyDeclaration> bodies = type.bodyDeclarations();
for (final BodyDeclaration body : bodies) {
if (body.getNodeType() == ASTNode.FIELD_DECLARATION) {
final FieldDeclaration field = (FieldDeclaration) body;
modifiers = (IExtendedModifier[]) field.modifiers().toArray(new IExtendedModifier[0]);
for (final IExtendedModifier modifier : modifiers) {
if (!(modifier instanceof Annotation)) {
continue;
}
final Annotation annotationModifer = (Annotation) modifier;
for (final Class<T> clazz : annotationTypes) {
if (annotationModifer.getTypeName().toString().equals(clazz.getCanonicalName())
|| annotationModifer.getTypeName().toString().equals(clazz.getSimpleName())) {
fields.add(field);
break;
}
}
}
}
}
}
}
return fields;
}
}
You cannot modify your code using annotation processing. However, you can create new classes, which can subclass the classes you have and they can contain additional annotations.
If you want to modify your code, you need a library that modifies your code either at compile time or at load time (for example as a special class loader).
I don't know what library would be the best for your case, but BCEL seems to be capable of the task.
See also:
asm
cglib
AspectJ
Create and Read J2SE 5.0 Annotations with the ASM Bytecode Toolkit
post-compilation removal of annotations from byte code
How do you use Java 1.6 Annotation Processing to perform compile time weaving?
Turning one annotation into many annotations with AspectJ

Categories

Resources