AdviceAdapter onMethodExit never called - java-bytecode-asm

I'm attempting to instrument a cassandra driver and in particular need to modify a ResultSet class to hang on to some information. In order to do this I need to modify the code where the instance is being allocated, which is a static method in another class. The code has this snippet in it:
return r.metadata.pagingState == null
? new SinglePage(columnDefs, tokenFactory, protocolVersion, columnDefs.codecRegistry, r.data, info)
: new MultiPage(columnDefs, tokenFactory, protocolVersion, columnDefs.codecRegistry, r.data, info, r.metadata.pagingState, session);
It also has other returns within the method. So my thought was to use an AdviceAdapter on this method and use the onMethodExit(). However, my method was never called. That seems absurd since.. the method has to be returning! After a little debugging, I find the visitInsn() in the AdviceAdapter class is being called just once, with an opcode of IALOAD (load an int from an array?).
I guess my question is.. what the hell is going on? Heh.. sorry, bonked my head on my desk a few too many times today.
EDIT: I changed my class to be a simple MethodVisitor just to see if could see more opcodes, and indeed I do! I see it all! I just no longer have access to dup(). :(

I have used an EmailAdviceAdaptor (for javax/mail/Transport) in one of my project, Code is given below. Hope this code will help to resolve your issue.
package com.mail.agent.adapter;
import com.mail.jtm.BTMConstants;
import com.mail.org.objectweb.asm.Label;
import com.mail.org.objectweb.asm.MethodVisitor;
import com.mail.org.objectweb.asm.Opcodes;
import com.mail.org.objectweb.asm.Type;
import com.mail.org.objectweb.asm.commons.AdviceAdapter;
public class MyEmailAdviceAdapter extends AdviceAdapter {
private String methodName;
private String className;
private String description;
private static final String MAIL_SEND_METHOD1_DESC="(Ljavax/mail/Message;)V";
private static final String MAIL_SEND_METHOD2_DESC="(Ljavax/mail/Message;[Ljavax/mail/Address;)V";
private static final String MAIL_SENDMESSAGE_METHOD_DESC="(Ljavax/mail/Message;[Ljavax/mail/Address;)V";
private boolean isSendMethod;
private int okFlag = newLocal(Type.BOOLEAN_TYPE);
Label startFinally = new Label();
public MyEmailAdviceAdapter(int access , MethodVisitor mv , String methodName, String description, String className, int classFileVersion){
super(Opcodes.ASM5 , mv, access, methodName, description);
this.className = className;
this.methodName = methodName;
this.description = description;
this.isSendMethod = false;
if(methodName.equals("send")){
if( description.equals(MAIL_SEND_METHOD1_DESC) || description.equals(MAIL_SEND_METHOD2_DESC)){
isSendMethod = true;
}
}
else if(methodName.equals("sendMessage") && description.equals(MAIL_SENDMESSAGE_METHOD_DESC)){
isSendMethod = true;
}
}
public void visitCode() {
super.visitCode();
mv.visitLabel(startFinally);
}
protected void onMethodEnter(){
if(isSendMethod) {
mv.visitInsn(Opcodes.ICONST_0);
mv.visitVarInsn(ISTORE, okFlag);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "mailMethodBegin", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z"; , false);
mv.visitVarInsn(ISTORE, okFlag);
}
}
protected void onMethodExit(int opcode){
if(opcode!=ATHROW) {
onFinally(opcode);
}
}
public void visitMaxs(int maxStack, int maxLocals){
Label endFinally = new Label();
mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null);
mv.visitLabel(endFinally);
onFinally(ATHROW);
mv.visitInsn(ATHROW);
mv.visitMaxs(maxStack, maxLocals);
}
private void onFinally(int opcode){
if(isSendMethod){
// If the method throws any exception
if(opcode == ATHROW){
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitVarInsn(ILOAD, okFlag);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "recordException", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false);
}
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitVarInsn(ILOAD, okFlag);
mv.visitLdcInsn(opcode);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "mailMethodEnd", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZI)V", false);
}
}
}

Related

A phone number synonym-like filter/tokenizer in Solr?

I'm trying to make Solr search phone numbers which are stored like this +79876543210 using a query like these:
+79876543210
79876543210
89876543210 <-- '+7' is replaced with region specific code '8'
9876543210 <-- '+7' entirely removed
This is just an example. Another one is wired line phone numbers:
+78662123456 <-- '+78662' is a specific region code
78662123456
88662123456
8662123456
123456 <-- region code entirely removed
One way I could manage this is using a separate field which is filled with these variants and used solely during search.
But this has issues with highlighting (it returns <em>123456</em> to be highlighted whereas the real value shown to user is +78662123456).
I thought that maybe it's best to make these indices using just Solr, but how?
First thought was to use managed synonyms filter and pass them along with each added record. But the docs explicitly states:
Changes made to managed resources via this REST API are not applied to the active Solr components until the Solr collection (or Solr core in single server mode) is reloaded.
So reloading a core every time after adding a record is not the way to go.
Other issues involve keeping these synonyms up to date with records.
Could there be another way to solve this?
Thanks to this comment (by MatsLindh) I've managed to assemble a simple filter based on bult-in EdgeNGramTokenFilter:
package com.step4;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReverseCustomFilter extends TokenFilter {
private static final PatternReplacementPair[] phonePatterns = {
new PatternReplacementPair("\\+7", "7"),
new PatternReplacementPair("\\+7", "8"),
new PatternReplacementPair("\\+7", ""),
new PatternReplacementPair("\\+78662", ""),
new PatternReplacementPair("\\+78663", ""),
};
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
private int curPatternIndex;
private int curPosIncr;
private State curState;
public ReverseCustomFilter(TokenStream input) {
super(input);
}
#Override
public final boolean incrementToken() throws IOException {
while (true) {
if (curPatternIndex == 0) {
if (!input.incrementToken()) {
return false;
}
curState = captureState();
curPosIncr += posIncrAtt.getPositionIncrement();
curPatternIndex = 1;
}
if (curPatternIndex <= phonePatterns.length) {
PatternReplacementPair replacementPair = phonePatterns[curPatternIndex - 1];
curPatternIndex++;
restoreState(curState);
Matcher matcher = replacementPair.getPattern().matcher(termAtt);
if (matcher.find()) {
posIncrAtt.setPositionIncrement(curPosIncr);
curPosIncr = 0;
String replaced = matcher.replaceFirst(replacementPair.getReplacement());
termAtt.setEmpty().append(replaced);
return true;
}
}
else {
restoreState(curState);
posIncrAtt.setPositionIncrement(0);
curPatternIndex = 0;
return true;
}
}
}
#Override
public void reset() throws IOException {
super.reset();
curPatternIndex = 0;
curPosIncr = 0;
}
#Override
public void end() throws IOException {
super.end();
posIncrAtt.setPositionIncrement(curPosIncr);
}
private static class PatternReplacementPair {
private final Pattern pattern;
private final String replacement;
public PatternReplacementPair(String pattern, String replacement) {
this.pattern = Pattern.compile(pattern);
this.replacement = replacement;
}
public Pattern getPattern() {
return pattern;
}
public String getReplacement() {
return replacement;
}
}
}

thelinmichael/spotify-web-api-java: How to get value from Async/Sync methods

For instance, I want to obtain the uri of a Spotify track and put it in another method as a String value, however I'm lost on how I'd go about doing that. I tried experimenting with SharedPreferences to get the value but getString method wasn't working. I was just wondering if there's a simpler way to getting say track.getUri (or any) in another method from the Async/Sync method. Any assistance would be greatly appreciated.
The code so far:
private static final String accessToken = "...";
private static final String id = "01iyCAUm8EvOFqVWYJ3dVX";
public static SharedPreferences.Editor editor;
private static final SpotifyApi spotifyApi = new SpotifyApi.Builder()
.setAccessToken(accessToken)
.build();
private static final GetTrackRequest getTrackRequest = spotifyApi.getTrack(id)
// .market(CountryCode.SE)
.build();
public static void getTrack_Sync() {
try {
final Track track = getTrackRequest.execute();
System.out.println("Name: " + track.getName());
} catch (IOException | SpotifyWebApiException | ParseException e) {
System.out.println("Error: " + e.getMessage());
}
}
#RequiresApi(api = Build.VERSION_CODES.N)
public void getTrack_Async() {
try {
final CompletableFuture<Track> trackFuture = getTrackRequest.executeAsync();
// Thread free to do other tasks...
// Example Only. Never block in production code.
final Track track = trackFuture.join();
String uri = track.getUri();
editor = getSharedPreferences("uri", 0).edit();
editor.putString("uri", uri);
editor.commit();
editor.apply();
System.out.println("Name: " + track.getUri());
} catch (CompletionException e) {
System.out.println("Error: " + e.getCause().getMessage());
} catch (CancellationException e) {
System.out.println("Async operation cancelled.");
}
}
public void go() {
getTrack_Async();
// String value = editor.getString("uri", )
}
To get the track you need some kind of information to start with. e.g. I have the spotify trackId and can find the track (synchronously) like this:
public Track getTrack(String trackId) {
return spotifyApi.getTrack(trackId).build().execute();
}
Now the Track object (specifically com.wrapper.spotify.model_objects.specification.Track) provides a lot of information. e.g. the field uri.
So you could do just:
public void run(String trackId) {
Track track = spotifyApi.getTrack(trackId).build().execute();
String uri = track.uri;
// now call something else with the uri?
}
Does that help? Your question was not entirely clear for me.

Mockito (How to correctly mock nested objects)

I have this next class:
#Service
public class BusinessService {
#Autowired
private RedisService redisService;
private void count() {
String redisKey = "MyKey";
AtomicInteger counter = null;
if (!redisService.isExist(redisKey))
counter = new AtomicInteger(0);
else
counter = redisService.get(redisKey, AtomicInteger.class);
try {
counter.incrementAndGet();
redisService.set(redisKey, counter, false);
logger.info(String.format("Counter incremented by one. Current counter = %s", counter.get()));
} catch (JsonProcessingException e) {
logger.severe(String.format("Failed to increment counter."));
}
}
// Remaining code
}
and this this my RedisService.java class
#Service
public class RedisService {
private Logger logger = LoggerFactory.getLogger(RedisService.class);
#Autowired
private RedisConfig redisConfig;
#PostConstruct
public void postConstruct() {
try {
String redisURL = redisConfig.getUrl();
logger.info("Connecting to Redis at " + redisURL);
syncCommands = RedisClient.create(redisURL).connect().sync();
} catch (Exception e) {
logger.error("Exception connecting to Redis: " + e.getMessage(), e);
}
}
public boolean isExist(String redisKey) {
return syncCommands.exists(new String[] { redisKey }) == 1 ? true : false;
}
public <T extends Serializable> void set(String key, T object, boolean convertObjectToJson) throws JsonProcessingException {
if (convertObjectToJson)
syncCommands.set(key, writeValueAsString(object));
else
syncCommands.set(key, String.valueOf(object));
}
// Remaining code
}
and this is my test class
#Mock
private RedisService redisService;
#Spy
#InjectMocks
BusinessService businessService = new BusinessService();
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void myTest() throws Exception {
for (int i = 0; i < 50; i++)
Whitebox.invokeMethod(businessService, "count");
// Remaining code
}
my problem is the counter always equals to one in logs when running tests
Counter incremented by one. Current counter = 1(printed 50 times)
and it should print:
Counter incremented by one. Current counter = 1
Counter incremented by one. Current counter = 2
...
...
Counter incremented by one. Current counter = 50
this means the Redis mock always passed as a new instance to BusinessService in each method call inside each loop, so how I can force this behavior to become only one instance used always for Redis inside the test method ??
Note: Above code is just a sample to explain my problem, but it's not a complete code.
Your conclusion that a new RedisService is somehow created in each iteration is wrong.
The problem is that it is a mock object for which you haven’t set any behaviours, so it responds with default values for each method call (null for objects, false for bools, 0 for ints etc).
You need to use Mockito.when to set behaviour on your mocks.
There is some additional complexity caused by the fact that:
you run the loop multiple times, and behaviour of the mocks differ between first and subsequent iterations
you create cached object in method under test. I used doAnswer to capture it.
You need to use doAnswer().when() instead of when().thenAnswer as set method returns void
and finally, atomicInt variable is modified from within the lambda. I made it a field of the class.
As the atomicInt is modified each time, I again used thenAnswer instead of thenReturn for get method.
class BusinessServiceTest {
#Mock
private RedisService redisService;
#InjectMocks
BusinessService businessService = new BusinessService();
AtomicInteger atomicInt = null;
#BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void myTest() throws Exception {
// given
Mockito.when(redisService.isExist("MyKey"))
.thenReturn(false)
.thenReturn(true);
Mockito.doAnswer((Answer<Void>) invocation -> {
atomicInt = invocation.getArgument(1);
return null;
}).when(redisService).set(eq("MyKey"), any(AtomicInteger.class), eq(false));
Mockito.when(redisService.get("MyKey", AtomicInteger.class))
.thenAnswer(invocation -> atomicInt);
// when
for (int i = 0; i < 50; i++) {
Whitebox.invokeMethod(businessService, "count");
}
// Remaining code
}
}
Having said that, I still find your code questionable.
You store AtomicInteger in Redis cache (by serializing it to String). This class is designed to be used by multiple threads in a process, and the threads using it the same counter need to share the same instance. By serializing it and deserializing on get, you are getting multiple instances of the (conceptually) same counter, which, to my eyes, looks like a bug.
smaller issue: You shouldn't normally test private methods
2 small ones: there is no need to instantiate the field annotated with #InjectMocks. You don't need #Spy as well.

VaryByParam fails if a param is a list

I've got this action in MVC
[OutputCache(Duration = 1200, VaryByParam = "*")]
public ActionResult FilterArea( string listType, List<int> designersID, int currPage = 1 )
{
// Code removed
}
that fails to present the correct HTML with url like
http://example.com/en-US/women/clothing?designersID=158
http://example.com/en-US/women/clothing?designersID=158&designersID=13
Is this a know bug of OutputCache in .NET cause cannot recognize VaryByParam with a list param or am I missing something?
I too had the same issue in MVC3 and I believe it's still the same case in MVC5.
Here is the setup I had.
Request
POST, Content-Type:application/json, passing in an array of string as the parameter
{ "options": ["option1", "option2"] }
Controller Method
[OutputCache(Duration = 3600, Location = OutputCacheLocation.Any, VaryByParam = "options")]
public ActionResult GetOptionValues(List<string> options)
I tried every option possible with OutputCache and it just wasn't caching for me. Binding worked fine for the actual method to work. My biggest suspicion was that OutputCache wasn't creating unique cache keys so I even pulled its code out of System.Web.MVC.OutputCache to verify. I've verified that it properly builds unique keys even when a List<string> is passed in. Something else is buggy in there but wasn't worth spending more effort.
OutputCacheAttribute.GetUniqueIdFromActionParameters(filterContext,
OutputCacheAttribute.SplitVaryByParam(this.VaryByParam);
Workaround
I ended up creating my own OutputCache attribute following another SO post. Much easier to use and I can go enjoy the rest of the day.
Controller Method
[MyOutputCache(Duration=3600)]
public ActionResult GetOptionValues(Options options)
Custom Request class
I've inherited from List<string> so I can call the overriden .ToString() method in MyOutputcache class to give me a unique cache key string. This approach alone has resolved similar issues for others but not for me.
[DataContract(Name = "Options", Namespace = "")]
public class Options: List<string>
{
public override string ToString()
{
var optionsString= new StringBuilder();
foreach (var option in this)
{
optionsString.Append(option);
}
return optionsString.ToString();
}
}
Custom OutputCache class
public class MyOutputCache : ActionFilterAttribute
{
private string _cachedKey;
public int Duration { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.Url != null)
{
var path = filterContext.HttpContext.Request.Url.PathAndQuery;
var attributeNames = filterContext.ActionParameters["Options"] as AttributeNames;
if (attributeNames != null) _cachedKey = "MYOUTPUTCACHE:" + path + attributeNames;
}
if (filterContext.HttpContext.Cache[_cachedKey] != null)
{
filterContext.Result = (ActionResult) filterContext.HttpContext.Cache[_cachedKey];
}
else
{
base.OnActionExecuting(filterContext);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Cache.Add(_cachedKey, filterContext.Result, null,
DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Default, null);
base.OnActionExecuted(filterContext);
}
}

JSF - CDI instancing again a session bean

The source code:
public class ReportGenerator implements Serializable {
private static final long serialVersionUID = -3995091296520157208L;
#Inject
private ReportCacheSession reportCacheSession;
#Inject
private UserSessionBean userSessionBean;
#Inject
private Instance<ReportBuilder> reportBuilderInstance;
public static final int BUILD_ERROR = 0;
public static final int BUILD_OK = 1;
public static final int BUILD_NOPAGES = 2;
private ReportBuilder reportBuilder = null;
private FileData build(String jasperName, Map<String, Object> params, String extension, boolean guardarCache, boolean inline) {
FileData fd = null;
reportBuilder = reportBuilderInstance.get();
if (reportBuilder != null) {
reportBuilder.jasperName = jasperName;
reportBuilder.emailName = SevUtils.getEmailName(userSessionBean.getUserInfo().getEmail());
reportBuilder.sessionId = JSFUtils.getSessionId();
reportBuilder.params = params;
reportBuilder.extension = extension;
//reportBuilder.config(jasperName, SevUtils.getEmailName(userSessionBean.getUserInfo().getEmail()), JSFUtils.getSessionId(), params, extension);
reportBuilder.start();
try {
reportBuilder.join();
} catch (InterruptedException ex) {
Logger.getLogger(ReportGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
fd = reportBuilder.getFileData();
}
if (fd != null && fd.getState() == BUILD_OK) {
fd.setInline(inline);
if (guardarCache) {
reportCacheSession.addReport(fd);
}
}
return fd;
}
}
reportBuilder.start(); is a new Thread to generate the report(s), the problem is when the line reportCacheSession.addReport(fd); is called CDI create a new instance each time, but ReportCacheSession is a session bean annotated with javax.inject.Named and javax.enterprise.context.SessionScoped.
I don't know why this happens, but my solution is add a new line, like this:
FileData fd = null;
reportCacheSession.toString(); //NEW LINE
reportBuilder = reportBuilderInstance.get();
reportCacheSession.toString(); create the instance of ReportCacheSession before my thread is called and all works OK...
How the new thread affects to CDI? Why CDI created a new instance of my session bean when I called the thread before?
UPDATE 08/15/12:
Ok, I have changed my code to use the EJB annotation #Asynchronous, in this case I have problem when I'm generating a large PDF report (the XLS report works without problem), the file's size is incomplete(less bytes) and when I try to open it this appear in blank... Maybe a problem/bug with JRExporter#exportReport method...
LAST UPDATE:
Ok, the report generation was my mistake... the question is which alternative is best to use EJB Asynchronous or JMS? Thanks to all, each comment have led me to find a good solution...

Resources