Finding a memory leak in java restful api - java

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

Related

Using Byte Buddy for Java Agent

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.

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>

Why does Hibernate transaction not update my database?

I'm having an issue with my login class. If a user unsuccessfully attempts to login with a username then that user account should be disabled. At runtime there are no errors but nothing changes in my database. Am I doing something wrong with updating my database through Hibernate, I thought I could just use my UserBean.class to access properties, change them and then commit the Hibernate transaction?
Here is my LoginDAO.class:
package com.sga.app.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.context.internal.ManagedSessionContext;
import org.hibernate.criterion.Restrictions;
import org.owasp.encoder.Encode;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import com.sga.app.beans.UserBean;
import com.sga.app.hibernate.HibernateUtil;
import com.sga.app.security.LoginFailureEventListener;
import com.sga.app.security.XssRequestWrapper;
#Component("loginDAO")
#Transactional
#Configuration
public class LoginDAO implements
ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
private int loginAttemptsThreshold;
private int failedLoginAttempts;
private static Logger logger = Logger
.getLogger(LoginFailureEventListener.class);
private static Session session;
private Criteria criteria;
private String username;
private boolean enabled;
private String forename;
private String authority;
private XssRequestWrapper xssReqWrapper;
private PreparedStatement prepStmtUsers;
private PreparedStatement prepStmtAuthorities;
private String URL = "jdbc:oracle:thin:system/sgaWebApp#localhost:1521/XE";
private String updateUsersStatement = "insert into users (username, password)
values (:username, :password)";
private String updateAuthoritiesStatement = "insert into authorities (username,
authority) values (:username, :authority)";
#Bean
public LoginDAO loginDAO() {
return new LoginDAO();
}
public void setLoginAttemptsThreshold(int threshold) {
this.loginAttemptsThreshold = threshold;
}
#Transactional
public void loginUser(UserBean user, BindingResult result) {
try {
Connection conn = DriverManager.getConnection(URL);
// clean out any possible XSS injection
String cleanUsernameValueInput = cleanOutXSSVulnerabilities("j_username");
String cleanPasswordValueInput = cleanOutXSSVulnerabilities("j_password");
// OWASP encoding
String safeUsername = Encode.forHtml(cleanUsernameValueInput);
prepStmtUsers.setString(1, safeUsername);
String safePassword = Encode.forHtml(cleanPasswordValueInput);
prepStmtUsers.setString(2, safePassword);
prepStmtAuthorities.setString(1, safeUsername);
String safeUserAuthority = Encode.forHtml(user.getAuthority());
prepStmtAuthorities.setString(2, safeUserAuthority);
// execute login process
prepStmtUsers = conn.prepareStatement(updateUsersStatement);
prepStmtAuthorities = conn
.prepareStatement(updateAuthoritiesStatement);
prepStmtUsers.executeUpdate();
prepStmtAuthorities.executeUpdate();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (AccessDeniedException accessDenied) {
accessDenied.printStackTrace();
}
}
private String cleanOutXSSVulnerabilities(String input) {
return xssReqWrapper.cleanXSS(input);
}
#Override
public void onApplicationEvent(
AuthenticationFailureBadCredentialsEvent event) {
if (event.getException().getClass()
.equals(UsernameNotFoundException.class)) {
return;
}
// print registration attempts to log file for security investigation if
// required
logger.info("Registration attempt failed: " + event.getException());
logger.info("Registration attempt number: " + event.getTimestamp());
String userId = event.getAuthentication().getName();
logger.info("FAILED LOGIN ATTEMPT NUMBER "
+ recordLoginAttempts(userId));
recordLoginAttempts(userId);
if (recordLoginAttempts(userId) >= loginAttemptsThreshold) {
lockoutUser(userId);
}
}
private int recordLoginAttempts(String userId) {
failedLoginAttempts++;
return failedLoginAttempts;
}
#SuppressWarnings("unchecked")
private ArrayList<UserBean> getUserAccountDetails(String input) {
ArrayList<UserBean> returnValues = new ArrayList<UserBean>();
session = HibernateUtil.createSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
ManagedSessionContext.bind(session);
session.beginTransaction();
criteria = session.createCriteria(UserBean.class);
List<UserBean> retrievedUser = criteria.add(
Restrictions.like("username", input)).list();
for (UserBean userDetails : retrievedUser) {
logger.debug("USERNAME INSIDE THE GET USER ACCOUNT DETAILS METHOD: "
+ userDetails.getUsername());
logger.debug("AUTHORITY INSIDE THE GET USER ACCOUNT DETAILS METHOD: "
+ userDetails.getAuthority());
returnValues.add(userDetails);
}
session.flush();
session.getTransaction().commit();
session.close();
return returnValues;
}
private void lockoutUser(String userId) {
ArrayList<UserBean> userAccountValues = getUserAccountDetails(userId);
session = HibernateUtil.createSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
ManagedSessionContext.bind(session);
session.beginTransaction();
for (UserBean user : userAccountValues) {
username = user.getUsername();
forename = user.getForename();
enabled = user.getEnabled();
authority = user.getAuthority();
logger.debug("USERNAME: " + username);
logger.debug("FORENAME: " + forename);
logger.debug("ENABLED BEFORE CHANGE: " + enabled);
user.setEnabled(false);
logger.debug("AUTHORITY BEFORE CHANGE: " + authority);
user.setAuthority("BLOCKED");
}
session.flush();
session.getTransaction().commit();
logger.debug("ENABLED AFTER CHANGE: " + enabled);
logger.debug("AUTHORITY AFTER CHANGE: " + authority);
session.close();
ManagedSessionContext.unbind(HibernateUtil.createSessionFactory());
}
}
I don't think you should be invoking openSession in that manner. I would highly suggest you rewrite the method to not do any "session" related work at all. Let Spring handle it, especially since you are already using #Transactional.
Either way, in the lockoutUser() method, the users you find, aren't bound to the session that gets created after it.
ArrayList<UserBean> userAccountValues = getUserAccountDetails(userId);
session = HibernateUtil.createSessionFactory().openSession();
So, later in the method when you update the user instances of that ArrrayList, the session doesn't realize that the user instances are to be persisted because the session was never tracking them.
Try to have only one session per thread. Each method can have its own transaction, but we rarely come across situations where we need more than one session in a thread. This doesn't seem to be that kind of a situation.
session.flush();
session.getTransaction().commit();
session.close();
Try to delete session.flush(); or put it after session.getTransaction().commit(); may work.
It doesn't appear that you call session.save(Object) or session.update(Object).

Validate every field in a single pass with SuperCSV

I'm trying to write a large number of rows (~2 million) from a database to a CSV file using SuperCSV. I need to perform validation on each cell as it is written, and the built-in CellProcessors do very nicely. I want to capture all the exceptions that are thrown by the CellProcessors so that I can go back to the source data and make changes.
The problem is that when there are multiple errors in a single row (e.g. The first value is out of range, the second value is null but shouldn't be), only the first CellProcessor will execute, and so I'll only see one of the errors. I want to process the whole file in a single pass, and have a complete set of exceptions at the end of it.
This is the kind of approach I'm trying:
for (Row row : rows) {
try {
csvBeanWriter.write(row, HEADER_MAPPINGS, CELL_PROCESSORS);
} catch (SuperCsvCellProcessorException e) {
log(e);
}
}
How can I achieve this? Thanks!
EDIT: Here is the code I wrote that's similar to Hound Dog's, in case it helps anyone:
import java.util.List;
import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.util.CsvContext;
public class ExceptionCapturingCellProcessor extends CellProcessorAdaptor {
private final List<Exception> exceptions;
private final CellProcessor current;
public ExceptionCapturingCellProcessor(CellProcessor current, CellProcessor next, List<Exception> exceptions) {
super(next);
this.exceptions = exceptions;
this.current = current;
}
#Override
public Object execute(Object value, CsvContext context) {
// Check input is not null
try {
validateInputNotNull(value, context);
} catch (SuperCsvCellProcessorException e) {
exceptions.add(e);
}
// Execute wrapped CellProcessor
try {
current.execute(value, context);
} catch (SuperCsvCellProcessorException e) {
exceptions.add(e);
}
return next.execute(value, context);
}
}
I'd recommend writing a custom CellProcessor to achieve this. The following processor can be placed at the start of each CellProcessor chain - it will simply delegate to the processor chained after it, and will suppress any cell processing exceptions.
package example;
import java.util.ArrayList;
import java.util.List;
import org.supercsv.cellprocessor.CellProcessorAdaptor;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.util.CsvContext;
public class SuppressException extends CellProcessorAdaptor {
public static List<SuperCsvCellProcessorException> SUPPRESSED_EXCEPTIONS =
new ArrayList<SuperCsvCellProcessorException>();
public SuppressException(CellProcessor next) {
super(next);
}
public Object execute(Object value, CsvContext context) {
try {
// attempt to execute the next processor
return next.execute(value, context);
} catch (SuperCsvCellProcessorException e) {
// save the exception
SUPPRESSED_EXCEPTIONS.add(e);
// and suppress it (null is written as "")
return null;
}
}
}
And here it is in action:
package example;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.constraint.StrMinMax;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCsvCellProcessorException;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
public class TestSuppressExceptions {
private static final CellProcessor[] PROCESSORS = {
new SuppressException(new StrMinMax(0, 4)),
new SuppressException(new NotNull()) };
private static final String[] HEADER = { "name", "age" };
public static void main(String[] args) throws Exception {
final StringWriter stringWriter = new StringWriter();
ICsvBeanWriter beanWriter = null;
try {
beanWriter = new CsvBeanWriter(stringWriter,
CsvPreference.STANDARD_PREFERENCE);
beanWriter.writeHeader(HEADER);
// set up the data
Person valid = new Person("Rick", 43);
Person nullAge = new Person("Lori", null);
Person totallyInvalid = new Person("Shane", null);
Person valid2 = new Person("Carl", 12);
List<Person> people = Arrays.asList(valid, nullAge, totallyInvalid,
valid2);
for (Person person : people) {
beanWriter.write(person, HEADER, PROCESSORS);
if (!SuppressException.SUPPRESSED_EXCEPTIONS.isEmpty()) {
System.out.println("Suppressed exceptions for row "
+ beanWriter.getRowNumber() + ":");
for (SuperCsvCellProcessorException e :
SuppressException.SUPPRESSED_EXCEPTIONS) {
System.out.println(e);
}
// clear ready for next row
SuppressException.SUPPRESSED_EXCEPTIONS.clear();
}
}
} finally {
beanWriter.close();
}
// CSV will have empty columns for invalid data
System.out.println(stringWriter);
}
}
Here's the suppressed exceptions output (row 4 has two exceptions, one for each column):
Suppressed exceptions for row 3:
org.supercsv.exception.SuperCsvConstraintViolationException: null value
encountered processor=org.supercsv.cellprocessor.constraint.NotNull
context={lineNo=3, rowNo=3, columnNo=2, rowSource=[Lori, null]}
Suppressed exceptions for row 4:
org.supercsv.exception.SuperCsvConstraintViolationException: the length (5)
of value 'Shane' does not lie between the min (0) and max (4) values (inclusive)
processor=org.supercsv.cellprocessor.constraint.StrMinMax
context={lineNo=4, rowNo=4, columnNo=2, rowSource=[Shane, null]}
org.supercsv.exception.SuperCsvConstraintViolationException: null value
encountered processor=org.supercsv.cellprocessor.constraint.NotNull
context={lineNo=4, rowNo=4, columnNo=2, rowSource=[Shane, null]}
And the CSV output
name,age
Rick,43
Lori,
,
Carl,12
Notice how the invalid values were written as "" because the SuppressException processor returned null for those values (not that you'd use the CSV output anyway, as it's not valid!).

how to profile a page request for a spring mvc app

what options do I have to profile a page request in a spring mvc app?
I want to get a breakdown of how long the page request takes, along with the various stages like how long it takes to render the freemarker template, hibernate db calls, etc.
We just accomplished something similar with an interceptor and a custom tag. This solution is "light" enough to be used in production, presents its data as HTML comments at the bottom of the response, and allows you to opt into the more verbose logging with a request parameter. You apply the interceptor below to all request paths you want to profile, and you add the custom tag to the bottom of the desired pages. The placement of the custom tag is important; it should be invoked as close to the end of request processing as possible, as it's only aware of time spent (and objects loaded) prior to its invocation.
package com.foo.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class PageGenerationTimeInterceptor extends HandlerInterceptorAdapter {
public static final String PAGE_START_TIME = "page_start_time";
public static final String PAGE_GENERATION_TIME = "page_generation_time";
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute(PAGE_START_TIME, new Long(System.currentTimeMillis()));
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
Long startTime = (Long) request.getAttribute(PAGE_START_TIME);
if (startTime != null) {
request.setAttribute(PAGE_GENERATION_TIME, new Long(System.currentTimeMillis() - startTime.longValue()));
}
}
}
The custom tag looks for the request attributes, and uses them to compute the handler time, the view time, and the total time. It can also query the current Hibernate session for first-level cache statistics, which can shed some light on how many objects were loaded by the handler and view. If you don't need the Hibernate information, you can delete the big if block.
package com.foo.web.taglib;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.ServletContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TryCatchFinally;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.engine.CollectionKey;
import org.hibernate.engine.EntityKey;
import org.hibernate.stat.SessionStatistics;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.tags.RequestContextAwareTag;
import com.foo.web.interceptor.PageGenerationTimeInterceptor;
public class PageInfoTag extends RequestContextAwareTag implements TryCatchFinally {
private static final long serialVersionUID = -8448960221093136401L;
private static final Logger LOGGER = LogManager.getLogger(PageInfoTag.class);
public static final String SESSION_STATS_PARAM_NAME = "PageInfoTag.SessionStats";
#Override
public int doStartTagInternal() throws JspException {
try {
JspWriter out = pageContext.getOut();
Long startTime = (Long)pageContext.getRequest().getAttribute(PageGenerationTimeInterceptor.PAGE_START_TIME);
Long handlerTime = (Long)pageContext.getRequest().getAttribute(PageGenerationTimeInterceptor.PAGE_GENERATION_TIME);
if (startTime != null && handlerTime != null) {
long responseTime = System.currentTimeMillis() - startTime.longValue();
long viewTime = responseTime - handlerTime;
out.append(String.format("<!-- total: %dms, handler: %dms, view: %dms -->", responseTime, handlerTime, viewTime));
}
if (ServletRequestUtils.getBooleanParameter(pageContext.getRequest(), SESSION_STATS_PARAM_NAME, false)) {
//write another long HTML comment with information about contents of Hibernate first-level cache
ServletContext servletContext = pageContext.getServletContext();
ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
String[] beans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
SessionFactory.class, false, false);
if (beans.length > 0) {
SessionFactory sessionFactory = (SessionFactory) context.getBean(beans[0]);
Session session = sessionFactory.getCurrentSession();
SessionStatistics stats = session.getStatistics();
Map<String, NamedCount> entityHistogram = new HashMap<String, NamedCount>();
out.append("\n<!-- session statistics:\n");
out.append("\tObject keys (").append(String.valueOf(stats.getEntityCount())).append("):\n");
for (Object obj: stats.getEntityKeys()) {
EntityKey key = (EntityKey)obj;
out.append("\t\t").append(key.getEntityName()).append("#").append(key.getIdentifier().toString()).append("\n");
increment(entityHistogram, key.getEntityName());
}
out.append("\tObject key histogram:\n");
SortedSet<NamedCount> orderedEntityHistogram = new TreeSet<NamedCount>(entityHistogram.values());
for (NamedCount entry: orderedEntityHistogram) {
out.append("\t\t").append(entry.name).append(": ").append(String.valueOf(entry.count)).append("\n");
}
Map<String, NamedCount> collectionHistogram = new HashMap<String, NamedCount>();
out.append("\tCollection keys (").append(String.valueOf(stats.getCollectionCount())).append("):\n");
for (Object obj: stats.getCollectionKeys()) {
CollectionKey key = (CollectionKey)obj;
out.append("\t\t").append(key.getRole()).append("#").append(key.getKey().toString()).append("\n");
increment(collectionHistogram, key.getRole());
}
out.append("\tCollection key histogram:\n");
SortedSet<NamedCount> orderedCollectionHistogram = new TreeSet<NamedCount>(collectionHistogram.values());
for (NamedCount entry: orderedCollectionHistogram) {
out.append("\t\t").append(entry.name).append(": ").append(String.valueOf(entry.count)).append("\n");
}
out.append("-->");
}
}
} catch (IOException e) {
LOGGER.error("Unable to write page info tag");
throw new RuntimeException(e);
}
return Tag.EVAL_BODY_INCLUDE;
}
protected void increment(Map<String, NamedCount> histogram, String key) {
NamedCount count = histogram.get(key);
if (count == null) {
count = new NamedCount(key);
histogram.put(key, count);
}
count.count++;
}
class NamedCount implements Comparable<NamedCount> {
public String name;
public int count;
public NamedCount(String name) {
this.name = name;
count = 0;
}
#Override
public int compareTo(NamedCount other) {
//descending count, ascending name
int compared = other.count - this.count;
if (compared == 0) {
compared = this.name.compareTo(other.name);
}
return compared;
}
}
}
Take a look here:
Profiling with Eclipse and remote profile agents on Linux
Tutorial: Profiling with TPTP and Tomcat
An introduction to profiling Java applications using TPTP
TPTP = Eclipse Test and Performance Tools Platform
More links to the stack:
Open Source Profilers in Java

Categories