Implement Indexer on an custom asset with Liferay 6.1 - java

i'd like to implement a index on a custom asset made for my project called "projet", i've already developped this class : (based on the liferay's bookmark indexer )
/**
* Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import javax.portlet.PortletURL;
import org.apache.log4j.Logger;
import com.liferay.portal.kernel.search.BaseIndexer;
import com.liferay.portal.kernel.search.BooleanQuery;
import com.liferay.portal.kernel.search.Document;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.search.Indexer;
import com.liferay.portal.kernel.search.SearchContext;
import com.liferay.portal.kernel.search.SearchEngineUtil;
import com.liferay.portal.kernel.search.Summary;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.model.Group;
import com.liferay.portal.service.ClassNameLocalServiceUtil;
import com.liferay.portal.service.GroupLocalServiceUtil;
import com.liferay.portal.util.PortletKeys;
public class ProjetIndexer extends BaseIndexer {
private static Logger LOGGER = Logger.getLogger(ProjetIndexer.class);
public static final String[] CLASS_NAMES = { Projet.class.getName() };
public String[] getClassNames() {
return CLASS_NAMES;
}
public String getPortletId() {
return Long.toString(ClassNameLocalServiceUtil.getClassNameId(Projet.class)) ;
}
#Override
protected void doDelete(Object obj) throws Exception {
LOGGER.info("doDelete");
Projet entry = (Projet) obj;
deleteDocument(entry.getCompanyId(), entry.getPrimaryKey());
}
#Override
protected Document doGetDocument(Object obj) throws Exception {
LOGGER.info("doGetDocument");
Projet entry = (Projet) obj;
Document document = getBaseModelDocument(getPortletId(), entry);
document.addText(Field.DESCRIPTION, "test123");
document.addText(Field.TITLE, "test123");
document.addKeyword(Field.TYPE, entry.getType());
document.addKeyword(Field.COMPANY_ID, entry.getCompanyId());
//what else ??
return document;
}
#Override
protected Summary doGetSummary(Document document, Locale locale,
String snippet, PortletURL portletURL) throws Exception {
LOGGER.info("doGetSummary");
String title = document.get(Field.TITLE);
String url = document.get(Field.URL);
String entryId = document.get(Field.ENTRY_CLASS_PK);
// portletURL.setParameter("struts_action",
// "/bookmarks/view_entry");TODO
portletURL.setParameter("entryId", entryId);
return new Summary(title, url, portletURL);
}
#Override
protected void doReindex(Object obj) throws Exception {
LOGGER.info("doReindex");
Projet entry = (Projet) obj;
Document document = getDocument(entry);
SearchEngineUtil.updateDocument(entry.getCompanyId(), document);
}
#Override
protected void doReindex(String className, long classPK) throws Exception {
LOGGER.info("doReindex");
Projet entry = ProjetLocalServiceUtil.getProjet(classPK);
doReindex(entry);
}
#Override
protected void doReindex(String[] ids) throws Exception {
long companyId = GetterUtil.getLong(ids[0]);
LOGGER.info("doReindex");
// reindexFolders(companyId);
reindexRoot(companyId);
}
#Override
protected String getPortletId(SearchContext searchContext) {
return getPortletId();
}
protected void reindexRoot(long companyId) throws Exception {
LOGGER.info("reindexRoot");
int groupCount = GroupLocalServiceUtil.getCompanyGroupsCount(companyId);
int groupPages = groupCount / Indexer.DEFAULT_INTERVAL;
for (int i = 0; i <= groupPages; i++) {
int groupStart = (i * Indexer.DEFAULT_INTERVAL);
int groupEnd = groupStart + Indexer.DEFAULT_INTERVAL;
reindexRoot(companyId, groupStart, groupEnd);
}
}
protected void reindexRoot(long companyId, int groupStart, int groupEnd)
throws Exception {
LOGGER.info("reindexRoot");
List<Group> groups = GroupLocalServiceUtil.getCompanyGroups(companyId,
groupStart, groupEnd);
for (Group group : groups) {
long groupId = group.getGroupId();
// long folderId =
// BookmarksFolderConstants.DEFAULT_PARENT_FOLDER_ID;
int entryCount = ProjetLocalServiceUtil.getEntriesCount(groupId);
int entryPages = entryCount / Indexer.DEFAULT_INTERVAL;
for (int i = 0; i <= entryPages; i++) {
int entryStart = (i * Indexer.DEFAULT_INTERVAL);
int entryEnd = entryStart + Indexer.DEFAULT_INTERVAL;
reindexEntries(companyId, groupId, entryStart, entryEnd);
}
}
}
protected void reindexEntries(long companyId, long groupId, int entryStart,
int entryEnd) throws Exception {
LOGGER.info("reindexEntries");
List<Projet> entries = ProjetLocalServiceUtil.getEntries(groupId,
entryStart, entryEnd);
if (entries.isEmpty()) {
return;
}
Collection<Document> documents = new ArrayList<Document>();
for (Projet entry : entries) {
Document document = getDocument(entry);
documents.add(document);
}
SearchEngineUtil.updateDocuments(companyId, documents);
}
}
and this is my liferay portlet.xml:
<indexer-class>path package .ProjetIndexer</indexer-class>
but it doesnt work. search portlet (bundled with liferay 6.0) does not retrieve my custom asset
any ideas? thank

My issue is solved by deleting data under /data/lucent/ Maybe my tests deleloppment was corrupted I've changed the method doGetDocument too, making sure that no empty field is set in the map. many thanks for your cooperation

Related

Exception: java.lang.NoClassDefFoundError: org.ehcache.CacheMangerBuilder

I'll preface this by saying "I have no idea what I'm doing" with java, let alone ehcache, but I'm trying to learn.
That said, I have this code I've written that ran fine until I added the ehcache stuff. I had been simply writing to and reading from files, which worked fine but slow, so I'm trying to speed things up by using cache instead.
When I run my program, I get the following error, and I don't understand what it's telling me is wrong:
Exception in thread "Connect thread - [6, 5]."
java.lang.NoClassDefFoundError: org.ehcache.CacheManagerBuilder
at com.ibm.tpf.internal.ZSTATEngine.doFilter(ZSTATEngine.java:24)
at com.ibm.tpf.etos.filter.FilterFramework.filterMessage(FilterFramework.java:229)
at com.ibm.tpf.etos.api.APIFramework.addMessage(APIFramework.java:304)
at com.ibm.tpf.etos.comm.ETOSConnection.addMessage(ETOSConnection.java:765)
at com.ibm.tpf.etos.comm.ETOSModel._connect(ETOSModel.java:528)
at com.ibm.tpf.etos.comm.ETOSModel$ConnectThread.run(ETOSModel.java:706)
Caused by: java.lang.NoClassDefFoundError:
org.ehcache.CacheMangerBuilder
at java.net.URLClassLoader.findClass(URLClassLoader.java:496)
at java.lang.ClassLoader.loadClass(ClassLoader.java:631)
at java.lang.ClassLoader.loadClass(ClassLoader.java:597)
... 6 more
I tried to mimic the cache code found on the ehcache 3.0 documentation page... but I must've done something horribly wrong. Anyone mind taking a look?
package com.ibm.tpf.internal;
import java.awt.Color;
import java.io.File;
import org.ehcache.*;
import org.ehcache.config.CacheConfigurationBuilder;
import com.ibm.tpf.etos.TPFFilter.*;
import com.ibm.tpf.etos.api.*;
import com.ibm.tpf.etos.filter.*;
public class ZSTATEngine implements ETOSFilterEngine {
FilterFramework fw = null;
String[] names = null;
public ZSTATEngine(FilterFramework filFW, String[] parms) {
super();
this.fw = filFW;
}
public MessageBlock doFilter(MessageBlock msgBlock) throws FilterRuntimeException {
File file = new File("{path omitted}\\FILTER.DAT");
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("csmpCache",CacheConfigurationBuilder.newCacheConfigurationBuilder().buildConfig(Long.class, String.class)).build(false);
cacheManager.init();
Cache<Long, String> csmpCache = cacheManager.getCache("csmpCache", Long.class, String.class);
if(msgBlock.getMsgID().equals("CSMP0097I")) {
csmpCache.put(1L, msgBlock.getMsg()); /* 1L is a key */
msgBlock.setSuppressed(TernarySwitch.ON);
}
else {
if(msgBlock.getFlag() == Constants.ETOS_ONE_MSG || msgBlock.getFlag() == Constants.ETOS_START_MSG) {
if(file.exists() && file.isFile()) {
String csmpValue = csmpCache.get(1L);
MessageBlock mbCSMP = new MessageBlock(csmpValue, Constants.ETOS_ONE_MSG);
mbCSMP.setForeground(Color.BLUE);
msgBlock.setForeground(Color.BLUE);
fw.addFilteredMessage(mbCSMP);
}
}
}
cacheManager.close();
return msgBlock; /* whatever gets returned is what the system prints */
}
private Color ColorStringInterpreter(String colorMsg) throws FilterRuntimeException {
if (colorMsg.toUpperCase().startsWith("TOS")) { /* if it starts with TOS, then we're using color names */
String[] colorParts = colorMsg.split("_",2);
String colorTxt = colorParts[1].toString().trim();
if (colorTxt.toUpperCase() != "NONE") {
Color finalColor = Colors.fromString(colorTxt);
return finalColor;
}
}
else {
String[] colorParts = colorMsg.split("_",3); /* otherwise we're using RGB values */
String sRed = colorParts[0].toString().trim();
String sGreen = colorParts[1].toString().trim();
String sBlue = colorParts[2].toString().trim();
int iRed = Integer.parseInt(sRed);
int iGreen = Integer.parseInt(sGreen);
int iBlue = Integer.parseInt(sBlue);
Color finalColor = new Color (iRed, iGreen, iBlue);
return finalColor;
}
return null;
}
public String getName() {
return null;
}
public void modifyState(Object[] newParams) throws FilterConfigurationException, FilterRuntimeException {
}
public boolean isActive() {
return false;
}
public void shutdown() {
}
}
Thank you for your time

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.

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

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())

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