-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Inconsistent audit table Hibernate - Envers on a Spring MVC
PostPosted: Fri Sep 05, 2014 2:17 pm 
Newbie

Joined: Wed Nov 21, 2012 5:29 am
Posts: 3
I'm using Spring Data JPA 1.6.4 with Hibernate 4.3.6.Final + envers into a Spring MVC 4 web application secured with Spring Security 3.2.5. The web application is deployed on a Tomcat 7.0.52 web container, configured with a JNDI datasource:

Code:
<Resource
              name="jdbc/appDB"
              auth="Container"
              factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
              type="javax.sql.DataSource"
              initialSize="4"
              maxActive="8"
              maxWait="10000"
              maxIdle="8"
              minIdle="4"
              username="user"
              password="password"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://ip/schema?zeroDateTimeBehavior=convertToNull"
              testOnBorrow="true"
              testWhileIdle="true"
              validationQuery="select 1"
              validationInterval="300000" />

I have a strange behaviour with audit table Customers_H: noticed that sometimes audit tables are populated in a wrong way by envers,

I have no idea of why and when It happens, but I have as a result a revision table like the following:

Code:
ID        ACTION TYPE        REV END        USER
23                       0                        256          U1
23                       2                        NULL        NULL
23                       0                        NULL         U2

The strange thing is that U1 is the owner of an entity with id = 6 (not of the entity with id = 23!), while U2 has really worked on entity ID 23. The problem is that the revision table is inconsinstent and then I have an Hibernate ASSERTION FAILURE

ERROR org.hibernate.AssertionFailure - HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): java.lang.RuntimeException: Cannot update previous revision for entity Customer_H and id 23.
This prohibit user to update the entity.

My problem is to investigate how this can happen!

Here is Customer domain:

Code:
@SuppressWarnings("serial")
@Entity
@Audited
public class Customer extends AbstractDomain{

    @ManyToOne(optional=false)
    @JoinColumn(updatable=false, nullable=false)
    @JsonIgnore
    private Company company;

    @OneToMany(mappedBy="customer", cascade=CascadeType.REMOVE)
    private Set<Plant> plants = new HashSet<Plant>();

    @Enumerated(EnumType.STRING)
    @Column(nullable=false)
    private CustomerType customerType;

    private String code;

    // other basic fields + getter and settes
}

Here is AbstractDomain class:

Code:
@SuppressWarnings("serial")
@MappedSuperclass
@Audited
public abstract class AbstractDomain implements Auditable<String, Long>, Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;   

    @Version
    @JsonIgnore
    private int version;

    @JsonIgnore
    @Column(updatable=false)
    private String createdBy;

    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    @DateTimeFormat(iso=ISO.DATE_TIME)
    @JsonIgnore
    @Column(updatable=false)
    private DateTime createdDate;

    @JsonIgnore
    private String lastModifiedBy;

    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    @DateTimeFormat(iso=ISO.DATE_TIME)
    @JsonIgnore
    private DateTime lastModifiedDate;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public int getVersion() {
        return version;
    }
    public void setVersion(int version) {
        this.version = version;
    }

    @Override
    public String getCreatedBy() {
        return createdBy;
    }
    @Override
    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    @Override
    public DateTime getCreatedDate() {
        return createdDate;
    }
    @Override
    public void setCreatedDate(DateTime createdDate) {
        this.createdDate = createdDate;
    }

    @Override
    public String getLastModifiedBy() {
        return lastModifiedBy;
    }
    @Override
    public void setLastModifiedBy(String lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
    }

    @Override
    public DateTime getLastModifiedDate() {
        return lastModifiedDate;
    }
    @Override
    public void setLastModifiedDate(DateTime lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    @Transient
    @Override
    public final boolean isNew() {
        if (id == null) {
            return true;
        } else {
            return false;
        }
    }   
}

Here is CustomerService:

Code:
@Service
@Repository
@Transactional(readOnly=true)
public class CustomerServiceImpl implements CustomerService{

    @Autowired
    private CustomerRepository customerRepository;

    @Override
    @PostAuthorize("@customerSecurityService.checkAuth(returnObject)")
    public Customer findById(Long id) {
        return customerRepository.findOne(id);
    }

    @Override
    @PreAuthorize("isAuthenticated()")
    @Transactional(readOnly=false)
    public Customer create(Customer entry) {
        entry.setCompany(SecurityUtils.getCustomer().getCompany());
        return customerRepository.save(entry);
    }

    @Override
    @PreAuthorize("@customerSecurityService.checkAuth(#entry)")
    @Transactional(readOnly=false)
    public Customer update(Customer entry) {
        return customerRepository.save(entry);
    }

    ....
}
Here is my CustomerRepository

public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>,  QueryDslPredicateExecutor<Customer> {

}

Here the Service I use to do security checks in @PreAuthorize @PostAuthorize annotations in CustomerService methods:

Code:
@Component
@Transactional(readOnly=true)
public class CustomerSecurityService {

    Logger LOGGER = LoggerFactory.getLogger(CustomerSecurityService.class);

    @Autowired
    private CustomerRepository customerRepository;

    public boolean checkAuth(Customer customer) {
        if(customer == null) {
            LOGGER.error("customer NULL!");
            return false;
        }


        if (customer.getId()==null) {
            return true;
        }


        if (customer.getId()!=null) {
            Customer dbCustomer = customerRepository.findOne(customer.getId());

            if (dbCustomer.getCompany().getId().equals( SecurityUtils.getCustomer().getCompany().getId())){
                return true;
            }else {
                return false;
            }
        }
        return false;
    }

    public boolean checkPage(Page<Customer> pages) {
        for(Customer customer : pages.getContent()) {
            Customer dbCustomer = customerRepository.findOne(customer.getId());

            if (!dbCustomer.getCompany().getId().equals(SecurityUtils.getCustomer().getCompany().getId())){
                return false;
            }
        }
        return true;
    }
}

My SecurityUtils class

Quote:
public class SecurityUtils {

private SecurityUtils(){}

private static Logger LOGGER = LoggerFactory.getLogger(SecurityUtils.class);

public static Customer getCustomer() {
Customer customer = null;
if (SecurityContextHolder.getContext().getAuthentication()!=null) {
customer = ((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getCustomer();
LOGGER.debug("Customer found: "+customer.getUserName());
}else {
LOGGER.debug("Customer not bound.");
}
return customer;
}


public static boolean isUserInRole(String role) {
for (GrantedAuthority grantedAuthority : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
if (grantedAuthority.getAuthority().equals(role)) {
return true;
}
}
return false;
}
}

And finally xml jpa configuration:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf"/>
    </bean>

    <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />

    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />

        <property name="packagesToScan" value="scan.domain"/>

        <property name="persistenceUnitName" value="persistenceUnit"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <!--${hibernate.format_sql} -->
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
                <!-- ${hibernate.show_sql} -->
                <prop key="hibernate.show_sql">false</prop>

                <prop key="hibernate.connection.charSet">UTF-8</prop>

                <prop key="hibernate.max_fetch_depth">3</prop>
                <prop key="hibernate.jdbc.fetch_size">50</prop>
                <prop key="hibernate.jdbc.batch_size">20</prop>

                <prop key="jadira.usertype.databaseZone">jvm</prop>

                <prop key="org.hibernate.envers.audit_table_suffix">_H</prop>
                <prop key="org.hibernate.envers.revision_field_name">AUDIT_REVISION</prop>
                <prop key="org.hibernate.envers.revision_type_field_name">ACTION_TYPE</prop>
                <prop key="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</prop>
                <prop key="org.hibernate.envers.audit_strategy_validity_end_rev_field_name">AUDIT_REVISION_END</prop>
                <prop key="org.hibernate.envers.audit_strategy_validity_store_revend_timestamp">True</prop>
                <prop key="org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name">AUDIT_REVISION_END_TS</prop>               
            </props>
        </property>
    </bean>

    <jpa:repositories base-package="scan.repository"
                      entity-manager-factory-ref="emf"
                      transaction-manager-ref="transactionManager"/>

    <jpa:auditing auditor-aware-ref="auditorAwareBean" />

    <bean id="auditorAwareBean" class="auditor.AuditorAwareBean"/>

</beans>

In the project I have about 50 domain classes, some of them with Inheritance Table_Per_Class.

The application is now used by few users, that are not connected at the same time. So I can say that only one user is using my application at a given time.

I also not understand how can I make an unsafe use of the Session. I never work directly with Hibernate Session. I always use a higher level abstraction with Spring Data Repositories. Sometimes I need to extends JpaRepository interface in order to call saveAndFlush() or explicitly call flush(). Maybe that the cause?
I can't understand this behaviour! Any suggestion would be appreciated!!


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.