OPENJPA-2030: Add a test for audit

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1354266 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2012-06-26 22:16:03 +00:00
parent 318642931b
commit 56bd02449a
5 changed files with 421 additions and 6 deletions

View File

@ -0,0 +1,119 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.audit;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.kernel.Audited;
import org.apache.openjpa.persistence.Type;
/**
* An example of an immutable persistent entity that holds a reference to the entity being audited.
* <br>
* This entity holds the reference to the entity being audited in a <em>generic</em>
* sense i.e. it does not know the exact type of the audited entity, but merely that
* it is a {@link PersistenceCapable} instance.
* <br>
* OpenJPA supports such reference by annotating with the {@link #audited reference field} as
* <tt>@Type(PersistenceCapable.class)</tt>.
* <br>
* The audit operation is also represented as a {@link #operation enumerated field}.
*
* @author Pinaki Poddar
*
*/
@Entity
public class AuditedEntry {
@Id
@GeneratedValue
private long id;
@ManyToOne(cascade=CascadeType.MERGE)
@Type(PersistenceCapable.class)
private Object audited;
@Enumerated(EnumType.STRING)
private AuditableOperation operation;
@Temporal(TemporalType.TIMESTAMP)
private Timestamp ts;
@ElementCollection
private List<String> updatedFields;
/**
* Constructs using an {@link Audited audited} instance.
* <br>
* An audited instances are supplied to the {@link Auditor#audit(Broker, Collection, Collection, Collection)
* auditor} by OpenJPA runtime within the transaction just before it is going to commit.
* <br>
* An audited instance carries the managed instance being audited in two <em>separate</em> references.
* The {link {@link Audited#getManagedObject()} first reference} is to the actual persistence instance
* being audited. The {link {@link Audited#getOriginalObject() second reference} is a <em>transient</em>
* copy of the actual persistence instance but in a state as it were when it entered the managed context
* i.e. when it was persisted or loaded from the database.
* <br>
* The {@link Audited} instance also knows the fields that were updated.
* @param a an audited instance.
*/
public AuditedEntry(Audited a) {
audited = a.getManagedObject();
ts = new Timestamp(new Date().getTime());
operation = a.getType();
if (operation == AuditableOperation.UPDATE) {
updatedFields = Arrays.asList(a.getUpdatedFields());
}
}
public Object getAudited() {
return audited;
}
public AuditableOperation getOperation() {
return operation;
}
public Timestamp getTimestamp() {
return ts;
}
public long getId() {
return id;
}
public List<String> getUpdatedFields() {
return updatedFields;
}
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.audit;
import java.util.Collection;
import org.apache.openjpa.kernel.Audited;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.lib.conf.Configuration;
/**
* Example of an {@link Auditor auditor} that records the audit entries in the same database
* of the managed entities being audited.
*
* @author Pinaki Poddar
*
*/
public class InplaceAuditor implements Auditor {
@Override
public void audit(Broker broker, Collection<Audited> newObjects, Collection<Audited> updates,
Collection<Audited> deletes) {
recordAudits(broker, newObjects);
recordAudits(broker, updates);
recordAudits(broker, deletes);
}
@Override
public boolean isRollbackOnError() {
return false;
}
/**
* Recording an audit is simply persisting an {@link AuditedEntry} with
* the available {@link Broker persistence context}.
* @param broker
* @param audits
*/
private void recordAudits(Broker broker, Collection<Audited> audits) {
for (Audited a : audits) {
broker.persist(new AuditedEntry(a), null);
}
}
// -------------------------------------------------------------
// Configurable implementation that does nothing.
// -------------------------------------------------------------
@Override
public void setConfiguration(Configuration conf) {
}
@Override
public void startConfiguration() {
}
@Override
public void endConfiguration() {
}
@Override
public void close() throws Exception {
}
}

View File

@ -0,0 +1,150 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.audit;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import junit.framework.TestCase;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAPersistence;
/**
* A test for audit facility.
*
* @author Pinaki Poddar
*
*/
public class TestAudit extends TestCase {
private static OpenJPAEntityManagerFactory emf;
private static Auditor auditor;
private static Object oid;
EntityManager em;
public void setUp() {
if (emf == null) {
emf = OpenJPAPersistence.cast(Persistence.createEntityManagerFactory("audit"));
assertNotNull(emf);
auditor = emf.getConfiguration().getAuditorInstance();
em = emf.createEntityManager();
clearAuditedEntries();
oid = createManagedObject();
} else {
em = emf.createEntityManager();
}
}
private Object createManagedObject() {
em.getTransaction().begin();
X x = new X();
x.setName("New Object");
x.setPrice(100);
em.persist(x);
em.getTransaction().commit();
return emf.getPersistenceUnitUtil().getIdentifier(x);
}
private void clearAuditedEntries() {
em.getTransaction().begin();
em.createQuery("delete from AuditedEntry a").executeUpdate();
em.getTransaction().commit();
}
public void testAuditorIsConfigured() {
assertNotNull(auditor);
}
public void testIsEntityAuditable() {
assertNotNull(X.class.getAnnotation(Auditable.class));
}
public void testNewInstancesAreAudited() {
X x = em.find(X.class, oid);
assertNotNull(x);
AuditedEntry entry = findLastAuditedEntry(AuditableOperation.CREATE);
assertNotNull(entry);
assertEquals(x, entry.getAudited());
assertEquals(AuditableOperation.CREATE, entry.getOperation());
assertEquals(X.class, entry.getAudited().getClass());
assertTrue(entry.getUpdatedFields().isEmpty());
}
public void testUpdateOutsideTransactionAreAudited() {
X x = em.find(X.class, oid);
assertNotNull(x);
x.setName("Updated Object outside transaction");
em.getTransaction().begin();
x = em.merge(x);
em.getTransaction().commit();
AuditedEntry entry = findLastAuditedEntry(AuditableOperation.UPDATE);
assertNotNull(entry);
assertEquals(x, entry.getAudited());
assertEquals(AuditableOperation.UPDATE, entry.getOperation());
assertTrue(entry.getUpdatedFields().contains("name"));
assertFalse(entry.getUpdatedFields().contains("price"));
}
public void testUpdateInsideTransactionAreAudited() {
X x = em.find(X.class, oid);
assertNotNull(x);
em.getTransaction().begin();
x.setPrice(x.getPrice()+100);
x = em.merge(x);
em.getTransaction().commit();
AuditedEntry entry = findLastAuditedEntry(AuditableOperation.UPDATE);
assertNotNull(entry);
assertEquals(x, entry.getAudited());
assertEquals(AuditableOperation.UPDATE, entry.getOperation());
assertFalse(entry.getUpdatedFields().contains("name"));
assertTrue(entry.getUpdatedFields().contains("price"));
}
/**
* Finds the latest audit entry of the given operation type.
* The <em>latest</em> is determined by a sort on identifier which is assumed to be monotonically ascending.
*
*/
AuditedEntry findLastAuditedEntry(AuditableOperation op) {
List<AuditedEntry> entry =
em.createQuery("select a from AuditedEntry a where a.operation=:op order by a.id desc", AuditedEntry.class)
.setMaxResults(1)
.setParameter("op", op)
.getResultList();
return entry.get(0);
}
}

View File

@ -0,0 +1,64 @@
package org.apache.openjpa.audit;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* A simple persistent entity used to test audit facility.
* An entity is annotated with {@link Auditable} annotation to qualify for audit.
*
* @author Pinaki Poddar
*
*/
@Entity
@Auditable
public class X {
@Id
@GeneratedValue
private long id;
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public long getId() {
return id;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
X other = (X) obj;
if (id != other.id)
return false;
return true;
}
public String toString() {
return "X[" + id + "]";
}
}

View File

@ -432,14 +432,16 @@
</properties>
</persistence-unit>
<persistence-unit name="query-result">
<mapping-file>META-INF/query-result-orm.xml</mapping-file>
<class>org.apache.openjpa.persistence.results.cls.ResultClsXml</class>
<persistence-unit name="audit">
<class>org.apache.openjpa.audit.X</class>
<class>org.apache.openjpa.audit.AuditedEntry</class>
<properties>
<property name="openjpa.jdbc.SynchronizeMappings"
value="buildSchema(ForeignKeys=true)"/>
<property name="openjpa.Auditor" value="org.apache.openjpa.audit.InplaceAuditor"/>
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/>
<property name="openjpa.DynamicEnhancementAgent" value="false"/>
</properties>
</persistence-unit>
</persistence>