Using Byte Buddy for Java Agent - java

I wish to create an agent to attach to our live Tomcat & Weblogic servers which will intercept all method calls to all classes declared in my companies package and do some logging of metrics such as execution time.
I came across the Byte Buddy library which seems to cater for this. However, I am not 100% clear on the approach to creating an agent using Byte Buddy:
The following article suggests that one creates an own agent and makes no mention of the byte-buddy-agent: http://mydailyjava.blogspot.ie/2015/01/make-agents-not-frameworks.html
However, I do see someone has created his/her own byte-buddy-agent so I am
not sure if I am meant to use this.
https://github.com/raphw/byte-buddy/tree/master/byte-buddy-agent
I went with the approach of creating my own agent and packaged it up using Maven to include Byte Buddy as a fat jar (so that the Byte Buddy code is on the class path) which I reference from my catalina.bat.
Edit: I have since downloaded the source and figured out that the AgentBuilder relies on the byte-buddy-agent package so the above question is irrelevant.
Tomcat starts up fine and I can see that the agent is called as I see the "Entered premain" System.out.
However I never see the "Intercepted" System.out when I execute the code on a separate war file deployed to Tomcat.
Edit: Code below updated based on Rafael's response and this is now working.
Can somebody tell me what I might be doing wrong here? I've included the agent code below.
Also, can someone tell me which ElementMatchers is best for package matching? I tried nameStartsWith but it had no effect and the API documentation does not state if it is the fully qualified class name.
*Edit: The nameStartsWith does check the package. *
Anyway, thanks in advance for any help!
package com.mycompany.agent;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.Callable;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;
public class MyAgent {
public static void premain(String agentArgument, Instrumentation instrumentation) {
System.out.println("Entered premain");
try{
new AgentBuilder.Default()
.withListener( new AgentBuilder.Listener() {
public void onComplete(String arg0) {
System.out.println("Completed - " + arg0);
}
public void onError(String arg0, Throwable arg1) {
System.out.println("Error - " + arg0+", "+arg1.getMessage());
arg1.printStackTrace();
}
public void onIgnored(String arg0) {
System.out.println("Ignored - " + arg0);
}
public void onTransformation(TypeDescription arg0, DynamicType arg1) {
System.out.println("Transformed - " + arg0+", type = "+arg1);
}
})
.rebase(ElementMatchers.nameStartsWith("com.mycompany"))
.transform(new AgentBuilder.Transformer() {
public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription) {
return builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(new Interceptor()));
}
}).installOn(instrumentation);
}
catch (RuntimeException e) {
System.out.println("Exception instrumenting code : "+e);
e.printStackTrace();
}
}
package com.mycompany.agent;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
#SuppressWarnings("rawtypes")
public class Interceptor {
#RuntimeType
public Object intercept( #SuperCall Callable<?> callable, #AllArguments Object[] allArguments, #Origin Method method, #Origin Class clazz) throws Exception {
long startTime = System.currentTimeMillis();
Object response;
try{
response = callable.call();
}
catch(Exception e) {
System.out.println("Exception occurred in method call: " + methodName(clazz, method, allArguments) + " Exception = " + e);
throw e;
}
finally{
System.out.println("Method " + methodName(clazz, method) + " completed in " + (System.currentTimeMillis() - startTime) + " miliseconds");
}
return response;
}
private String methodName(Class clazz, Method method){
return methodName(clazz, method, null);
}
private String methodName(Class clazz, Method method, Object[] allArguments){
StringBuilder builder = new StringBuilder();
builder.append(clazz.getName());
builder.append(".");
builder.append(method.getName());
builder.append("(");
for(int i = 0; i < method.getParameters().length; i++) {
builder.append(method.getParameters()[i].getName());
if(allArguments != null) {
Object arg = allArguments[i];
builder.append("=");
builder.append(arg != null ? arg.toString() : "null");
}
if(i < method.getParameters().length - 1) {
builder.append(", ");
}
}
builder.append(")");
return builder.toString();
}

Everything seems to be right. You should always try registering an AgentBuider.Listener which will expose stack traces of unsuccessful instrumentations if Byte Buddy causes an exception for signaling an illegal instrumentation attempt.
I assume that your class's package-private definition of your Interceptor is the cause of this exception. Your interceptor must be visible to all instrumented code. Otherwise, the class is not invokable.

Related

Cannot invoke observeForever on a background thread in React-Native

We are trying to integrate a native android framework in react-native using a .aar file. All the steps leading to the integration work fine, i.e. the .aar file gets successfully integrated into the project and the methods are visible throughout the project.
We have completed the steps present at - https://reactnative.dev/docs/native-modules-android
However when we are trying to run some methods that are a part of the .aar framework, we run into an error -
Error: Exception in HostFunction: java.lang.IllegalStateException: Cannot invoke observeForever on a background thread
The code implementation looks like this-
package com.frameworksample;
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.companyName.sdk.Environment;
import com.companyName.sdk.FrameworkKit;
public class CompanyModule extends ReactContextBaseJavaModule {
CompanyModule(ReactApplicationContext context) {
super(context);
}
#NonNull
#Override
public String getName() {
return "CompanyModule";
}
#ReactMethod
public void logMessage(String name, String message) {
Log.d("Company Module", "Logged a message with the name " + name
+ " and message: " + message);
}
public Runnable initializeSDK(String paramOne, String paramTwo) {
TokenProvider tokenProvider = new TokenProvider();
FrameworkKit.initialise(
this.getReactApplicationContext(),
paramOne,
paramTwo,
tokenProvider,
response -> {
listProfiles();
Log.d("Framework Kit Object", "Object initialized successfully" + response);
return null;
}
);
return null;
}
public Runnable listProfiles() {
Log.d("List profile thread", "" + Thread.currentThread());
FrameworkKit.getInstance().listProfiles("employee#comanpany.com", response -> {
Log.d("List Profiles", "Response - " + response);
return null;
});
return null;
}
#ReactMethod(isBlockingSynchronousMethod = true)
public void initCompanyKit(String paramOne, String paramTwo){
Log.d("init method thread", "" + Thread.currentThread());
runOnUiThread(initializeSDK(paramOne, paramTwo));
}
}
Even though the function listProfiles is running on the main thread, an error is thrown for
Cannot invoke observeForever on a background thread
What can be the reasons for this behaviour and how can we rectify this.
You return null for Runnables, that could be the reason. You can try to return Runnable and override run method of it.
private Runnable initializeSDK(String name) {
return new Runnable() {
#Override
public void run() {
// Your code
}
};
}

What is the purpose of CompletableFuture's complete method?

I've been doing some reading about CompletableFuture.
As of now I understand that CompletableFuture is different from Future in a sense that it provides means to chain futures together, to use callback to handle Future's result without actually blocking the code.
However, there is this complete() method that I'm having a hard time wrapping my head around. I only know that it allows us to complete a future manually, but what is the usage for it? The most common examples I found for this method is when doing some async task, we can immediately return a string for example. But what is the point of doing so if the return value doesn't reflect the actual result? If we want to do something asynchronously why don't we just use regular future instead? The only use I can think of is when we want to conditionally cancel an ongoing future. But I think I'm missing some important key points here.
complete() is equivalent to the function transforming the previous stage's result and returning getResponse("a1=Chittagong&a2=city")
response, you can run this method in a different thread
when getResponse() methods response available then thenApply() will be invoked to print log.
no one will be blocked if you run getResponse(String url) in a different thread.
This example shows a scenario where we are printing a log while getting responses from complete();
Code
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class CompletableFutureEx {
Logger logger = Logger.getLogger(CompletableFutureEx.class.getName());
public static void main(String[] args) {
new CompletableFutureEx().completableFutureEx();
}
private void completableFutureEx() {
var completableFuture = new CompletableFuture<String>();
completableFuture.thenApply(response -> {
logger.log(Level.INFO, "Response : " + response);
return response;
});
//some long process response
try {
completableFuture.complete(getResponse("a1=Chittagong&a2=city"));
} catch (Exception e) {
completableFuture.completeExceptionally(e);
}
try {
System.out.println(completableFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
private String getResponse(String url) throws URISyntaxException, IOException, InterruptedException {
var finalUrl = "http://localhost:8081/api/v1/product/add?" + url;
//http://localhost:8081/api/v1/product/add?a1=Chittagong&a2=city
var request = HttpRequest.newBuilder()
.uri(new URI(finalUrl)).GET().build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("response body " + response.body());
return response.body();
}
}

Guaranteed to run function before AWS lambda exits

Is there a way in the JVM to guarantee that some function will run before a AWS lambda function will exit? I would like to flush an internal buffer to stdout as a last action in a lambda function even if some exception is thrown.
As far as I understand you want to execute some code before your Lambda function is stopped, regardless what your execution state is (running/waiting/exception handling/etc).
This is not possible out of the box with Lambda, i.e. there is no event fired or something similar which can be identified as a shutdown hook. The JVM will be freezed as soon as you hit the timeout. However, you can observe the remaining execution time by using the method getRemainingTimeInMillis() from the Context object. From the docs:
Returns the number of milliseconds left before the execution times out.
So, when initializing your function you can schedule a task which is regularly checking how much time is left until your Lambda function reaches the timeout. Then, if only less than X (milli-)seconds are left, you do Y.
aws-samples shows how to do it here
package helloworld;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
/**
* Handler for requests to Lambda function.
*/
public class App implements RequestHandler<Object, Object> {
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
#Override
public void run() {
System.out.println("[runtime] ShutdownHook triggered");
System.out.println("[runtime] Cleaning up");
// perform actual clean up work here.
try {
Thread.sleep(200);
} catch (Exception e) {
System.out.println(e);
}
System.out.println("[runtime] exiting");
System.exit(0);
}
});
}
public Object handleRequest(final Object input, final Context context) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("X-Custom-Header", "application/json");
try {
final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents);
return new GatewayResponse(output, headers, 200);
} catch (IOException e) {
return new GatewayResponse("{}", headers, 500);
}
}
private String getPageContents(String address) throws IOException {
URL url = new URL(address);
try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
return br.lines().collect(Collectors.joining(System.lineSeparator()));
}
}
}

Finding a memory leak in java restful api

Im an amateur programming ambitious to learn, but I've encountered a new type of problem that I don't even know where to begin to look - memory leaks in java. I've searched around and can't really find anything that would help me. I used Tomcat v9.0 and Java 1.8. I don't even know what code you need to see in order to help.
I get this warning when im trying to send a request to my REST api
VARNING: The web application [School] appears to have started a thread named [pool-2-thread-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
java.lang.Thread.run(Unknown Source)
The server can work for one or two requests then it just stops. Since im new to this type of problem I have no idea what might cause it, and searching around didn't really help me in my amateurish ways. I'm guessing however that im creating threads in some way but they're not being closed.
The controller i tried to reach with a get method
package se.consys.controllers;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.persistence.NoResultException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import se.consys.Entities.Course;
import se.consys.Entities.Lecture;
import se.consys.Entities.Student;
import se.consys.Entities.Teacher;
import se.consys.Utilities.HibernateUtility;
import se.consys.dataaccess.DaoGenericHibernateImpl;
import se.consys.params.LocalDateParam;
import se.consys.params.LocalDateTimeParam;
import se.consys.params.MapHelper;
import se.consys.services.GenericService;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
#SuppressWarnings("rawtypes, unchecked")
#Path("courses")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class CourseController {
private GenericService courseService = GenericService.getGenericService(new DaoGenericHibernateImpl<>(Course.class));
private GenericService teacherService = GenericService.getGenericService(new DaoGenericHibernateImpl<>(Teacher.class));
private GenericService studentService = GenericService.getGenericService(new DaoGenericHibernateImpl<>(Student.class));
private String noCourseFoundMsg = "No course found with the specified id.";
#GET
public Response getAll() {
List<Course> courses = courseService.findAll();
return Response.status(200).build();
}
#GET
#Path("/{id}")
public Response getById(#PathParam("id") int id) {
try {
Course course = (Course) courseService.findById(id);
return Response.ok().entity(course).build();
} catch (NoResultException e) {
System.out.println(noCourseFoundMsg);
return Response.status(204).build();
}
}
#SuppressWarnings("unchecked")
#POST
public Response create(Course entity) {
courseService.create(entity);
return Response.status(201).entity(entity).build();
}
#PATCH
#Path("/{id}")
public Response partialUpdate(#DefaultValue("0") #PathParam("id") int id,
#DefaultValue("null") #QueryParam("name") String courseName,
#DefaultValue("-1") #QueryParam("duration") int durationInMonths,
#DefaultValue("") #QueryParam("end") LocalDateParam endDate,
#DefaultValue("") #QueryParam("start") LocalDateParam startDate,
#DefaultValue("") #QueryParam("timestamp") LocalDateTimeParam timeStamp,
#DefaultValue("-1") #QueryParam("supervisor") int supervisor)
{
Course courseToBeUpdated = (Course) courseService.findById(id);
System.out.println(courseName);
if (courseName != null) courseToBeUpdated.setCourseName(courseName);
if (durationInMonths != -1) courseToBeUpdated.setDurationInMonths(durationInMonths);
if (endDate != null && !endDate.getLocalDate().equals(LocalDate.MIN)) courseToBeUpdated.setEndDate(endDate.getLocalDate());
if (startDate != null && !startDate.getLocalDate().equals(LocalDate.MIN)) courseToBeUpdated.setStartDate(startDate.getLocalDate());
if (timeStamp != null && !timeStamp.getLocalDateTime().equals(LocalDateTime.MIN)) courseToBeUpdated.setTimeStamp(timeStamp.getLocalDateTime());
if (supervisor != -1) courseToBeUpdated.setSupervisor((Teacher) teacherService.findById(supervisor));
courseService.update(courseToBeUpdated);
return Response.status(200).build();
}
#PATCH
#Path("/{id}/students")
public Response partialUpdateOnStudents(
#DefaultValue("0") #PathParam("id") int id,
#DefaultValue("null") #QueryParam("update") String studentString) {
String[] seperatedIds = studentString.split("-");
List<Integer> studentIds = new ArrayList<Integer>();
for (int i = 0; i < seperatedIds.length; i++) {
studentIds.add((int) Integer.parseInt(seperatedIds[i]));
}
List<Student> allStudents = studentService.findAll();
List<Student> StudentsToAddIntoCourse = new ArrayList<Student>();
for (int i = 0; i < allStudents.size(); i++) {
for(int j = 0; j < studentIds.size(); j++) {
if (allStudents.get(i).getId() == studentIds.get(j)) {
StudentsToAddIntoCourse.add(allStudents.get(i));
}
}
}
Course courseToBeUpdated = (Course) courseService.findById(id);
if (studentString != null) courseToBeUpdated.setStudents(StudentsToAddIntoCourse);
courseService.update(courseToBeUpdated);
return Response.status(200).build();
}
#PUT
#Path("/{id}")
public Response update(#DefaultValue("0") #PathParam("id") int id, Course entity) {
try {
Course courseToBeUpdated = (Course) courseService.findById(id);
courseToBeUpdated.setCourseName(entity.getCourseName());
courseToBeUpdated.setDurationInMonths(entity.getDurationInMonths());
courseToBeUpdated.setEndDate(entity.getEndDate());
courseToBeUpdated.setScheduledLectures(entity.getScheduledLectures());
courseToBeUpdated.setStartDate(entity.getStartDate());
courseToBeUpdated.setStudents(entity.getStudents());
courseToBeUpdated.setSupervisor(entity.getSupervisor());
courseToBeUpdated.setTimeStamp(entity.getTimeStamp());
courseService.update(courseToBeUpdated);
return Response.status(200).entity(entity).build();
} catch (NoResultException e) {
System.out.println(noCourseFoundMsg);
return Response.ok().status(204).build();
}
}
#DELETE
#Path("/{id}")
public Response delete(#DefaultValue("0") #PathParam("id") int id) {
try {
Course courseToBeDeleted = (Course) courseService.findById(id);
courseService.delete(courseToBeDeleted);
return Response.status(200).build();
} catch (NoResultException e) {
System.out.println(noCourseFoundMsg);
return Response.status(204).build();
}
}
}
I have a suspicion that the problem actually is my generic dao and dao service that probably is completely wrong but works on paper. Im new to generics and managed to throw something together.
DaoGenericHibernateImpl
package se.consys.dataaccess;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import org.hibernate.Session;
import se.consys.Utilities.Helper;
import se.consys.Utilities.HibernateUtility;
import se.consys.services.GenericService;
public class DaoGenericHibernateImpl<T extends Serializable> implements IGenericDao<T> {
Session session = HibernateUtility.getSessionFactory().openSession();
private String activeClassName;
private String wrongClassError = "ERROR: Wrong class used on the established service.";
public DaoGenericHibernateImpl(Class<T> type) {
activeClassName = type.getSimpleName();
}
#Override
public void create(T entity) {
if (entity.getClass().getSimpleName().equals(activeClassName)) {
session.beginTransaction();
session.persist(entity);
session.getTransaction().commit();
} else {
System.out.println(wrongClassError + " Entity has not been saved to the database. "
+ "Class used: " + entity.getClass().getSimpleName() + ". "
+ "Class expected: " + activeClassName + ".");
}
}
#Override
public T update(T entity) {
if (entity.getClass().getSimpleName().equals(activeClassName)) {
session.beginTransaction();
session.merge(entity);
session.getTransaction().commit();
return entity;
} else {
System.out.println(wrongClassError + " Entity has not been updated. "
+ "Class used: " + entity.getClass().getSimpleName() + ". "
+ "Class expected: " + activeClassName + ".");
}
return entity;
}
#Override
public void delete(T entity) {
if (entity.getClass().getSimpleName().equals(activeClassName)) {
session.beginTransaction();
//session.update(entity);
session.delete(entity);
session.getTransaction().commit();
} else {
System.out.println(wrongClassError + " Entity has not been deleted. "
+ "Class used: " + entity.getClass().getSimpleName() + ". "
+ "Class expected: " + activeClassName + ".");
}
}
#Override
public T findById(int id) {
final String HQL_BY_ID = "FROM " + activeClassName + " WHERE id=:id";
#SuppressWarnings("unchecked")
T result = (T) session.createQuery(HQL_BY_ID)
.setParameter("id", id)
.setMaxResults(1)
.getSingleResult();
return result;
}
#Override
public List<T> findAll() {
String HQL_FIND_ALL = "FROM " + activeClassName;
#SuppressWarnings("unchecked")
List<T> result = (List<T>) session.createQuery(HQL_FIND_ALL)
.getResultList();
return result;
}
#Override
public void removeReference(T entity, Class<?> reference) {
Method setter = Helper.findSetter(entity, reference);
try {
setter.invoke(entity, null);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e.getMessage());
}
}
}
I feel like i can just keep adding codeblocks so ill add a link to my github where everything is, hope that is ok.
Thanks for the help.
In general, the first thing you need to think about in this case is that if you can't reason about the memory safety and/or thread safety of your code then you've actually got two problems to solve.
In terms of what you're actually seeing, what you need are the standard tools for identifying what threads are running and what memory is in use. You can find out what threads are running with a tool like jconsole (GUI) or jstack (CLI). They're both included as a standard part of the JDK and will give you a stack trace of all the threads running in the system. They won't necessarily tell you why those threads exist, but the stack traces themselves may help you identify where they're coming from.
The problem you're describing sounds like a runaway thread although it could also be memory related, and the error message suggests that's a likely root cause, so it may also be useful to look into the memory usage. For that, you can also use jconsole (profile section) or jhat & jmap (if you're a CLI-fan). Either of those will tell you what objects exist in the VM heap and what type they are. This information can be incredibly useful but also incredibly distracting - most heaps are dominated by Strings, Maps and Lists, because that's what most programs are built out of. Still, you can often gain useful insights by comparing the difference between two profiles, one taken when 'everything seems fine' and one taken when 'its stopped working'.
Those sorts of tools can help you identify a problem in a running system. Once you've done that you probably want to look at why it wasn't clear from the program text that there was going to be a thread-pool or memory usage issue. Things like using a Map as a cache is a big one. Or implementing equals/hashcode in terms of mutable fields. For threads, anything which may not terminate can jam up a thread pool pretty quickly - blocking IO in a web-server (for example).

NoClassDefFoundError: com/google/common/reflect/TypeToken

I have been using this api. The API is a Java wrapper for Mailchimp API with maven dependency.
<dependency>
<groupId>com.ecwid</groupId>
<artifactId>ecwid-mailchimp</artifactId>
<version>2.0.1.0</version>
</dependency>
I didn’t have trouble working with their API so far. But now I see this strange exception:
Exception in thread "Timer-2" java.lang.NoClassDefFoundError: com/google/common/reflect/TypeToken
at com.ziprealty.subscription.MailChimpNewsSubscriptionProcessor.updateAllUnSubscribedEmails(MailChimpNewsSubscriptionProcessor.java:84)
at com.ziprealty.job.MailChimpSubscriptionProcessor.processTask(MailChimpSubscriptionProcessor.java:29)
at com.ziprealty.job.JobBase.run(JobBase.java:96)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Caused by: java.lang.ClassNotFoundException: com.google.common.reflect.TypeToken
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1305)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1157)
... 5 more
And here is the code for updateAllUnSubscribedEmails
public void updateAllUnSubscribedEmails( Date lastRunDate, String brandCode,Logger logger){
logger.log(Level.SEVERE, "Entering mail chimp subscription processor in boardwalk for :" + brandCode);
logger.log(Level.SEVERE, "Last run date is :" + lastRunDate);
System.out.println("Entering mail chimp subscription processor in boardwalk for :" + brandCode);
try {
MailChimpSubscriptionDAO subscriptionDAO = MailChimpSubscriptionDAO.INSTANCE;
MailChimpSubscription subscription= subscriptionDAO.getMailChimpSubscriptionByBrandCode(brandCode);
logger.log(Level.SEVERE,"Subscription object is :"+ subscription);
**ListMembersMethod listMembersMethod= new ListMembersMethod();**
logger.log(Level.SEVERE,"listMembersMethod object is :"+ listMembersMethod);
listMembersMethod.status= MemberStatus.unsubscribed;
logger.log(Level.SEVERE,"listMembersMethod.status object is :"+ listMembersMethod.status);
listMembersMethod.apikey=mailChimpApiKey;
logger.log(Level.SEVERE,"listMembersMethod.apikey object is :"+ listMembersMethod.apikey);
listMembersMethod.id=subscription.getEmailListId();
logger.log(Level.SEVERE,"listMembersMethod.id object is :"+ listMembersMethod.id);
listMembersMethod.since= lastRunDate;
.
.
.
.
} catch (IOException e) {
e.printStackTrace();
System.out.println(e.getMessage());
logger.log(Level.SEVERE, e.getMessage());
} catch (MailChimpException e) {
e.printStackTrace();
System.out.println(e.getMessage());
logger.log(Level.SEVERE, e.getMessage());
}
catch (Exception e){
e.printStackTrace();
System.out.println(e.getMessage());
logger.log(Level.SEVERE, e.getMessage());
}
finally {
logger.log(Level.SEVERE,"Finally block ...Try catch block ended");
}
logger.log(Level.SEVERE,"After finally Try catch block without exception ");
}
The code stops working at this line :
ListMembersMethod listMembersMethod= new ListMembersMethod();
It doesn’t even go to the Exceptions block at all. Only to the finally block.
This is the generated code by Intelij IDEA for the class ListMembersMethod:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.ecwid.mailchimp.method.v1_3.list;
import com.ecwid.mailchimp.MailChimpAPIVersion;
import com.ecwid.mailchimp.MailChimpMethod.Method;
import com.ecwid.mailchimp.MailChimpObject.Field;
import com.ecwid.mailchimp.method.v1_3.list.HasListIdMethod;
import com.ecwid.mailchimp.method.v1_3.list.ListMembersResult;
import com.ecwid.mailchimp.method.v1_3.list.MemberStatus;
import java.util.Date;
#Method(
name = "listMembers",
version = MailChimpAPIVersion.v1_3
)
public class ListMembersMethod extends HasListIdMethod<ListMembersResult> {
#Field
public MemberStatus status;
#Field
public Date since;
#Field
public Integer start;
#Field
public Integer limit;
public ListMembersMethod() {
}
}
Intelij Idea has also generated the following code for hasHasListIdMethod :
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.ecwid.mailchimp.method.v1_3.list;
import com.ecwid.mailchimp.MailChimpMethod;
import com.ecwid.mailchimp.MailChimpObject.Field;
public abstract class HasListIdMethod<R> extends MailChimpMethod<R> {
#Field
public String id;
public HasListIdMethod() {
}
}
The MailChimpMethod has the following code where it contains TypeToken
:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.ecwid.mailchimp;
import com.ecwid.mailchimp.MailChimpAPIVersion;
import com.ecwid.mailchimp.MailChimpObject;
import com.ecwid.mailchimp.MailChimpObject.Field;
import com.google.common.reflect.TypeToken;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class MailChimpMethod<R> extends MailChimpObject {
private final TypeToken<R> resultTypeToken = new TypeToken(this.getClass()) {
};
#Field
public String apikey;
public MailChimpMethod() {
}
public final MailChimpMethod.Method getMetaInfo() {
for(Class c = this.getClass(); c != null; c = c.getSuperclass()) {
MailChimpMethod.Method a = (MailChimpMethod.Method)c.getAnnotation(MailChimpMethod.Method.class);
if(a != null) {
return a;
}
}
throw new IllegalArgumentException("Neither " + this.getClass() + " nor its superclasses are annotated with " + MailChimpMethod.Method.class);
}
public final Type getResultType() {
Type type = this.resultTypeToken.getType();
if(!(type instanceof Class) && !(type instanceof ParameterizedType) && !(type instanceof GenericArrayType)) {
throw new IllegalArgumentException("Cannot resolve result type: " + this.resultTypeToken);
} else {
return type;
}
}
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface Method {
MailChimpAPIVersion version();
String name();
}
}
I would really appreciate your help on this. Couldn't figure out what the fix would be.
The MailChimp API Wrapper 2.0.1.0 depends on Guava 16.0.1 (see mvnrepository.com/artifact/com.ecwid/ecwid-mailchimp/2.0.1.0). The com.google.common.reflect.TypeToken class is part of Guava 16.0.1 (see central.maven.org/maven2/com/google/guava/guava/16.0.1/guava-16.0.1.jar).
Do you use Maven to build your project and if this is the case, can you compile/test your code without issues? Is Guava downloaded for the project? What do you see when running mvn dependency:tree -Dverbose?
It took me sometime but I found the issue. I was working on two independent projects which are dependent on MailChimp API. However, I didn't have the maven dependency on one of the project and that was causing the problem.
So included the maven dependency on both projects, and it worked like a charm!
<dependency>
<groupId>com.ecwid</groupId>
<artifactId>ecwid-mailchimp</artifactId>
<version>2.0.1.0</version>
</dependency>

Categories