mirror of https://github.com/apache/openjpa.git
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:
parent
318642931b
commit
56bd02449a
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue