2

I want to to implement an audit logging module to my existing system and I want to save actual logged user information and it's only the httpSession.

I'm using hibernate/spring/struts 2, and in order to get actual logged user information and call saveLog service I need the ServletContext to find those service bean or get the httpServletRequest...

I have been searching and seems binding the session to ThreadLocal usign Filter is the only way? something like this or this (last answer)

is there other suggestion? is this a commun pattern or a good practice ?

Community
  • 1
  • 1
Yichz
  • 9,250
  • 10
  • 54
  • 92

4 Answers4

4

Spring can bind current request to the thread out of the box. If you use DispatcherServlet, it's done automatically, otherwise you need to declare RequestContextFilter.

Then you can access request properties via RequestContextHolder.

axtavt
  • 239,438
  • 41
  • 511
  • 482
0

Example for spring boot (spring-boot-starter 1.2.4.RELEASE).

In some controller:

@RequestMapping("login")
public UserBean login(@RequestParam("email") String email,
                      @RequestParam("password") String password,
                      HttpSession session) {
    // getting usser
    session.setAttribute("currentUser", user);
    return user;
}

Register hibernate listeners

import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.*;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.jpa.HibernateEntityManagerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;

@Component
public class UiDateListener implements PostLoadEventListener, PreUpdateEventListener {
    @Inject EntityManagerFactory entityManagerFactory;

    @PostConstruct
    private void init() {
        HibernateEntityManagerFactory hibernateEntityManagerFactory = (HibernateEntityManagerFactory) this.entityManagerFactory;
        SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
        EventListenerRegistry registry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
        registry.appendListeners(EventType.POST_LOAD, this);
        registry.appendListeners(EventType.PRE_UPDATE, this);
    }

    @Override
    public void onPostLoad(PostLoadEvent event) {
        final Object entity = event.getEntity();
        if (entity == null) return;

        final UserBean currentUser = (UserBean) RequestContextHolder.currentRequestAttributes().getAttribute("currentUser", RequestAttributes.SCOPE_SESSION);
        // some logic after entity loaded
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        final Object entity = event.getEntity();
        if (entity == null) return false;

        // some logic before entity persist

        return false;
    }
}
Enginer
  • 3,048
  • 1
  • 26
  • 22
0

Depending on your version of Hibernate, you may be able to use Envers to accommodate fine-grained audit logging. This includes the ability to add the 'current user' from a session variable into the given Revision:

@Entity
@RevisionEntity(ExampleListener.class)
public class ExampleRevEntity extends DefaultRevisionEntity {
    private String username;

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
}

This integrates with Hibernate nicely through a series of eventListeners, which you can call out in Spring like so:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...
    <property name="eventListeners">
        <map>
            <entry key="post-insert" value-ref="enversEventListener"/>
            <entry key="post-update" value-ref="enversEventListener"/>
            <entry key="post-delete" value-ref="enversEventListener"/>
            <entry key="pre-collection-update" value-ref="enversEventListener"/>
            <entry key="pre-collection-remove" value-ref="enversEventListener"/>
            <entry key="post-collection-recreate" value-ref="enversEventListener"/>
        </map>
    </property>
</bean>

Then you can query for audit revisions through the Envers query api.

After using this on a couple of recent projects, it's my preferred audit technique when using Hibernate.

To answer your question, you can then setup a Hibernate Interceptor or Envers RevisionListener to access the 'current user' by looking it up from the current Spring context:

applicationContext.getBean("currentUser", User.class);

as long as your user is setup as a scoped bean in Spring.

John Ericksen
  • 10,995
  • 4
  • 45
  • 75
  • I have been looking into Envers, but as far as I know it will have to create 2 tables for every entity and that's not what I want. I would like the audit log save into only one table. – Yichz Nov 30 '11 at 16:11
  • Saving into multiple tables (1 for audited entities, 1 for audit history and 1 for central revision ticket), for us, had a bunch of advantages. Keeping the audit history out of the live entities is a performance aid and helps keep your domain and controller code properly segmented: separating history from actual records. – John Ericksen Dec 01 '11 at 01:32
0

There was this article how to implement the Hibernate Listener as Spring bean to have full access to the Spring Context.

This may open complete new ways in your scenario (Depending on your app).

Ralph
  • 118,862
  • 56
  • 287
  • 383