You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pp...@apache.org on 2011/08/25 03:06:40 UTC

svn commit: r1161349 - in /openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa: conf/ kernel/

Author: ppoddar
Date: Thu Aug 25 01:06:39 2011
New Revision: 1161349

URL: http://svn.apache.org/viewvc?rev=1161349&view=rev
Log:
OPENJPA-2030: First version of Audit facility

Added:
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AuditManager.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Audited.java
Modified:
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java?rev=1161349&r1=1161348&r2=1161349&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java Thu Aug 25 01:06:39 2011
@@ -21,6 +21,8 @@ package org.apache.openjpa.conf;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.openjpa.kernel.AuditManager;
+import org.apache.openjpa.audit.Auditor;
 import org.apache.openjpa.datacache.CacheDistributionPolicy;
 import org.apache.openjpa.datacache.DataCache;
 import org.apache.openjpa.datacache.DataCacheManager;
@@ -1863,5 +1865,20 @@ public interface OpenJPAConfiguration
      */
     public InstrumentationManager getInstrumentationManagerInstance();
     
+    
+    /**
+     * Gets an instance of {@link AuditManager} associated with this configuration.
+     * 
+     * @since 2.2.0
+     */
+    public Auditor getAuditorInstance();
+    
+    /**
+     * Gets the plug-in string of {@link AuditManager} specified in this configuration.
+     * 
+     * @since 2.2.0
+     */
+    public String getAuditor();
+    
 }
 

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java?rev=1161349&r1=1161348&r2=1161349&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java Thu Aug 25 01:06:39 2011
@@ -23,6 +23,8 @@ import java.util.HashSet;
 import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.openjpa.audit.AuditLogger;
+import org.apache.openjpa.audit.Auditor;
 import org.apache.openjpa.datacache.CacheDistributionPolicy;
 import org.apache.openjpa.datacache.ConcurrentDataCache;
 import org.apache.openjpa.datacache.ConcurrentQueryCache;
@@ -98,6 +100,7 @@ public class OpenJPAConfigurationImpl
     public BrokerValue brokerPlugin;
     public ObjectValue dataCachePlugin;
     public ObjectValue dataCacheManagerPlugin;
+    public ObjectValue auditorPlugin;
     public ObjectValue cacheDistributionPolicyPlugin;
     public IntValue dataCacheTimeout;
     public ObjectValue queryCachePlugin;
@@ -588,6 +591,13 @@ public class OpenJPAConfigurationImpl
         instrumentationProviders.setAliases(aliases);
         instrumentationProviders.setInstantiatingGetter("getInstrumentationInstances");
         
+        auditorPlugin = addPlugin("Auditor", true);
+        aliases = new String[] { "default", AuditLogger.class.getName(), };
+        auditorPlugin.setAliases(aliases);
+        auditorPlugin.setDefault(aliases[0]);
+        auditorPlugin.setString(aliases[0]);
+        auditorPlugin.setInstantiatingGetter("getAuditor");
+        
         // initialize supported options that some runtimes may not support
         supportedOptions.add(OPTION_NONTRANS_READ);
         supportedOptions.add(OPTION_OPTIMISTIC);
@@ -1807,5 +1817,17 @@ public class OpenJPAConfigurationImpl
     public Map<String, Object> getPersistenceEnvironment() {
         return _peMap;
     }
+    
+    public Auditor getAuditorInstance() {
+    	Auditor auditor = (Auditor) auditorPlugin.get();
+        if (auditor == null) {
+            auditor = (Auditor) auditorPlugin.instantiate(Auditor.class, this);
+       }
+       return auditor;
+    }
+    
+    public String getAuditor() {
+    	return auditorPlugin.getString();
+    }
 }
 

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java?rev=1161349&r1=1161348&r2=1161349&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AbstractBrokerFactory.java Thu Aug 25 01:06:39 2011
@@ -33,7 +33,6 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 
-import javax.naming.NamingException;
 import javax.transaction.Status;
 import javax.transaction.Synchronization;
 import javax.transaction.Transaction;
@@ -41,9 +40,9 @@ import javax.transaction.TransactionMana
 
 import org.apache.commons.collections.set.MapBackedSet;
 import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.exception.NestableRuntimeException;
+import org.apache.openjpa.kernel.AuditManager;
+import org.apache.openjpa.audit.Auditor;
 import org.apache.openjpa.conf.BrokerValue;
-import org.apache.openjpa.conf.MetaDataRepositoryValue;
 import org.apache.openjpa.conf.OpenJPAConfiguration;
 import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
 import org.apache.openjpa.conf.OpenJPAVersion;
@@ -67,7 +66,6 @@ import org.apache.openjpa.meta.MetaDataM
 import org.apache.openjpa.meta.MetaDataRepository;
 import org.apache.openjpa.util.GeneralException;
 import org.apache.openjpa.util.InvalidStateException;
-import org.apache.openjpa.util.MetaDataException;
 import org.apache.openjpa.util.OpenJPAException;
 import org.apache.openjpa.util.UserException;
 
@@ -237,7 +235,7 @@ public abstract class AbstractBrokerFact
         if (remote.areRemoteEventsEnabled())
             broker.addTransactionListener(remote);
 
-        loadPersistentTypes(broker.getClassLoader());
+       loadPersistentTypes(broker.getClassLoader());
         _brokers.add(broker);
         _conf.setReadOnly(Configuration.INIT_STATE_FROZEN);
     }
@@ -486,8 +484,7 @@ public abstract class AbstractBrokerFact
         else
             bv = (BrokerValue) _conf.getValue(BrokerValue.KEY);
 
-        if (FinalizingBrokerImpl.class.isAssignableFrom(
-            bv.getTemplateBrokerType(_conf))) {
+        if (FinalizingBrokerImpl.class.isAssignableFrom(bv.getTemplateBrokerType(_conf))) {
             return MapBackedSet.decorate(new ConcurrentHashMap(), new Object() { });
         } else {
             return new ConcurrentReferenceHashSet<Broker>(ConcurrentReferenceHashSet.WEAK);
@@ -854,6 +851,13 @@ public abstract class AbstractBrokerFact
      * This method is invoked AFTER a BrokerFactory has been instantiated. 
      */
     public void postCreationCallback() {
+    	Auditor auditor = _conf.getAuditorInstance();
+    	if (auditor != null) {
+    		AuditManager auditManager = new AuditManager();
+    		auditManager.setAuditor(auditor);
+    		addLifecycleListener(auditManager, null);
+    		addTransactionListener(auditManager);
+    	}
         if (_conf.isInitializeEagerly()) {
             newBroker(_conf.getConnectionUserName(), _conf.getConnectionPassword(),
                 _conf.isConnectionFactoryModeManaged(), _conf.getConnectionRetainModeConstant(), false).close();

Added: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AuditManager.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AuditManager.java?rev=1161349&view=auto
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AuditManager.java (added)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/AuditManager.java Thu Aug 25 01:06:39 2011
@@ -0,0 +1,385 @@
+/*
+ * 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.kernel;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.openjpa.audit.Auditable;
+import org.apache.openjpa.audit.AuditableOperation;
+import org.apache.openjpa.audit.Auditor;
+import org.apache.openjpa.enhance.PCRegistry;
+import org.apache.openjpa.enhance.PCRegistry.RegisterClassListener;
+import org.apache.openjpa.enhance.PersistenceCapable;
+import org.apache.openjpa.enhance.StateManager;
+import org.apache.openjpa.event.LifecycleEvent;
+import org.apache.openjpa.event.LifecycleListener;
+import org.apache.openjpa.event.TransactionEvent;
+import org.apache.openjpa.event.TransactionListener;
+
+/**
+ * Controller for audit facility.
+ * This controller performs the following basic duties:
+ * <LI> Records auditable types at class laoding time
+ * <LI> Listens to instance life cycle changes and transaction.
+ * <LI> Collects auditable instances on instance life cycle changes.
+ * <LI> Delegates real auditing to the {@link Auditor} at transaction boundary.
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+public class AuditManager extends InMemorySavepointManager 
+	implements LifecycleListener, TransactionListener, RegisterClassListener {
+	
+	private Auditor _auditor;
+	private final Set<Class<?>> _allTypes;
+	private final Set<Class<?>> _newTypes;
+	private final Set<Class<?>> _updateTypes;
+	private final Set<Class<?>> _deleteTypes;
+	private final Map<Broker, Set<Audited>> _saved;
+	private final ReentrantLock _lock = new ReentrantLock();
+	
+	public AuditManager() {
+		super();
+		setPreFlush(false);
+		_allTypes = new HashSet<Class<?>>();
+		_newTypes = new HashSet<Class<?>>();
+		_updateTypes = new HashSet<Class<?>>();
+		_deleteTypes = new HashSet<Class<?>>();
+		_saved = new ConcurrentHashMap<Broker, Set<Audited>>();
+		PCRegistry.addRegisterClassListener(this);
+	}
+	
+	/**
+	 * Records all auditable classes in operation-specific sets. 
+	 */
+	@Override
+	public void register(Class<?> cls) {
+		Auditable auditable = cls.getAnnotation(Auditable.class);
+		if (auditable == null) {
+			return;
+		}
+		
+		List<AuditableOperation> events = Arrays.asList(auditable.values());
+		if (events.contains(AuditableOperation.ALL) || events.contains(AuditableOperation.CREATE)) {
+			_newTypes.add(cls);
+			_allTypes.add(cls);
+		}
+		if (events.contains(AuditableOperation.ALL) || events.contains(AuditableOperation.UPDATE)) {
+			_updateTypes.add(cls);
+			_allTypes.add(cls);
+		}
+		if (events.contains(AuditableOperation.ALL) || events.contains(AuditableOperation.DELETE)) {
+			_deleteTypes.add(cls);
+			_allTypes.add(cls);
+		}
+	}
+	
+	public void setAuditor(Auditor auditor) {
+		_auditor = auditor;
+	}
+	
+	public Auditor getAuditor() {
+		return _auditor;
+	}
+
+	public Set<Class<?>> getAuditedTypes() {
+		return Collections.unmodifiableSet(_allTypes);
+	}
+
+	/**
+	 * Transaction callbacks.
+	 */
+	@Override
+	public void beforeCommit(TransactionEvent event) {
+		if (_auditor == null) return;
+		_lock.lock();
+		try {
+			Broker broker = (Broker)event.getSource();
+			Set<Audited> audits = _saved.get(broker);
+			if (audits == null) return;
+			Collection<Audited> news = new HashSet<Audited>();
+			Collection<Audited> updates = new HashSet<Audited>();
+			Collection<Audited> deletes = new HashSet<Audited>();
+			Collection<?> instances = event.getTransactionalObjects();
+			for (Object instance : instances) {
+				StateManagerImpl sm = getImpl(instance);
+				if (sm != null) {
+					Audited audited = search(audits, sm);
+					if (audited == null) {
+						continue;
+					}
+					
+					if (sm.getPCState().isNew()) {
+						news.add(audited);
+					} else if (sm.getPCState().isDeleted()) {
+						deletes.add(audited);
+					} else if (sm.getPCState().isDirty()) {
+						updates.add(audited);
+					}
+				}
+			}
+			try {
+				_auditor.audit(broker, news, updates, deletes);
+			} catch (Exception e) {
+				if (_auditor.isRollbackOnError()) {
+					throw new RuntimeException("dump", e);
+				} else {
+					e.printStackTrace();
+				}
+			}
+		} finally {
+			_lock.unlock();
+		}
+	}
+	
+	@Override
+	public void afterCommit(TransactionEvent event) {
+		_saved.remove(event.getSource());
+	}
+
+	@Override
+	public void afterRollback(TransactionEvent event) {
+		_saved.remove(event.getSource());
+	}
+
+	@Override
+	public void afterBegin(TransactionEvent event) {
+	}
+
+	@Override
+	public void beforeFlush(TransactionEvent event) {
+	}
+
+	@Override
+	public void afterFlush(TransactionEvent event) {
+	}
+
+	@Override
+	public void afterStateTransitions(TransactionEvent event) {
+	}
+
+	@Override
+	public void afterCommitComplete(TransactionEvent event) {
+	}
+
+	@Override
+	public void afterRollbackComplete(TransactionEvent event) {
+	}
+
+	/**
+	 * Life-cycle callbacks 
+	 */
+	@Override
+	public void afterLoad(LifecycleEvent event) {
+		save(AuditableOperation.ALL, event);
+	}
+
+	@Override
+	public void afterPersist(LifecycleEvent event) {
+		save(AuditableOperation.CREATE, event);
+	}
+
+	@Override
+	public void beforeDelete(LifecycleEvent event) {
+		save(AuditableOperation.DELETE, event);
+	}
+
+	@Override
+	public void beforeDirty(LifecycleEvent event) {
+		if (!isAudited(event)) {
+			save(AuditableOperation.UPDATE, event);
+		}
+	}
+
+	@Override
+	public void beforePersist(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterRefresh(LifecycleEvent event) {
+	}
+
+	@Override
+	public void beforeStore(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterStore(LifecycleEvent event) {
+	}
+
+	@Override
+	public void beforeClear(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterClear(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterDelete(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterDirty(LifecycleEvent event) {
+	}
+
+	@Override
+	public void beforeDirtyFlushed(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterDirtyFlushed(LifecycleEvent event) {
+	}
+
+	@Override
+	public void beforeDetach(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterDetach(LifecycleEvent event) {
+	}
+
+	@Override
+	public void beforeAttach(LifecycleEvent event) {
+	}
+
+	@Override
+	public void afterAttach(LifecycleEvent event) {
+	}
+	
+	/**
+	 * Support functions.
+	 */
+	
+	/**
+	 * Extracts the persistence capable instance from the source of the given event.
+	 * @return null if an instance can not be extracted.
+	 */
+	protected PersistenceCapable getPersistenceCapable(LifecycleEvent evt) {
+		Object source = evt.getSource();
+		return source instanceof PersistenceCapable ? (PersistenceCapable)source : null;
+	}
+	
+	/**
+	 * Affirms if source of the given event is being audited already.
+	 */
+	protected boolean isAudited(LifecycleEvent event) {
+		StateManagerImpl sm = getImpl(event.getSource());
+		if (_saved.containsKey(sm.getBroker())) {
+			return search(_saved.get(sm.getBroker()), sm) != null;
+		}
+		return false;
+	}
+	
+	/**
+	 * Extracts the broker from the given persistence capable instance.
+	 * @param pc a persistence capable instance
+	 * @return null if a Broker can notbe extracted
+	 */
+	protected Broker getBroker(PersistenceCapable pc) {
+		if (pc == null) return null;
+		Object ctx = pc.pcGetGenericContext();
+		return ctx instanceof Broker ? (Broker)ctx : null;
+	}
+	
+	/**
+	 * Saves the source of the given event for auditing.
+	 * @param lc
+	 * @param event
+	 */
+	protected void save(AuditableOperation lc, LifecycleEvent event) {
+		StateManagerImpl sm = getImpl(event.getSource());
+		if (sm != null && isAuditable(lc, sm)) {
+			Broker broker = sm.getBroker();
+			
+			OpenJPASavepoint savepoint = newSavepoint("", broker);
+			savepoint.save(Collections.singleton(sm));
+			Map<StateManagerImpl, SavepointFieldManager> states = savepoint.getStates();
+			Map.Entry<StateManagerImpl, SavepointFieldManager> e = states.entrySet().iterator().next();
+			PersistenceCapable copy = e.getValue().getCopy();
+			copy.pcReplaceStateManager(null);
+			Audited audited = new Audited(sm, copy);
+			Set<Audited> audits = _saved.get(broker);
+			if (audits == null) {
+				audits = new HashSet<Audited>();
+				_saved.put(broker, audits);
+			}
+			audits.add(audited);
+		}
+	}
+	
+	/**
+	 * Searches the set of Auditable instances for a matching Statemanager. 
+	 * @param audits
+	 * @param sm
+	 * @return
+	 */
+	private Audited search(Set<Audited> audits, StateManagerImpl sm) {
+		for (Audited audit : audits) {
+			if (audit.getManagedObject() == sm.getPersistenceCapable()) {
+				return audit;
+			}
+		}
+		return null;
+	}
+	
+	/**
+	 * Gets an implementation.
+	 * @param instance
+	 * @return
+	 */
+	private StateManagerImpl getImpl(Object instance) {
+		if (instance instanceof PersistenceCapable) {
+			StateManager sm = ((PersistenceCapable)instance).pcGetStateManager();
+			if (sm instanceof StateManagerImpl) {
+				return (StateManagerImpl)sm;
+			} else {
+				return null;
+			}
+		} else {
+			return null;
+		}
+	}
+	
+	/**
+	 * Affirms if the given state manager is auditable for the given operation.
+	 * @param op an auditable operation
+	 * @param sm
+	 * @return
+	 */
+	protected boolean isAuditable(AuditableOperation op, StateManagerImpl sm) {
+		if (sm == null)
+			return false;
+		Class<?> cls  = sm.getMetaData().getDescribedType();
+		return (op == AuditableOperation.ALL    && _allTypes.contains(cls)
+			 ||	op == AuditableOperation.CREATE && _newTypes.contains(cls)) 
+		     ||(op == AuditableOperation.UPDATE && _updateTypes.contains(cls)) 
+		     ||(op == AuditableOperation.DELETE && _deleteTypes.contains(cls));
+	}
+	
+
+}

Added: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Audited.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Audited.java?rev=1161349&view=auto
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Audited.java (added)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Audited.java Thu Aug 25 01:06:39 2011
@@ -0,0 +1,93 @@
+/*
+ * 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.kernel;
+
+import java.util.BitSet;
+
+import org.apache.openjpa.audit.AuditableOperation;
+import org.apache.openjpa.enhance.PersistenceCapable;
+
+/**
+ * Carries immutable information about an audited persistent instance.
+ *  
+ * @author Pinaki Poddar
+ *
+ */
+public final class Audited {
+	private final StateManagerImpl _sm;
+	private final PersistenceCapable _original;
+	
+	/**
+	 * Supply a state manager and a transient copy.
+	 * @param sm a state manager, must not be null.
+	 * @param o the transient copy
+	 */
+	Audited(StateManagerImpl sm, PersistenceCapable o) {
+		if (sm == null || o == null)
+			throw new NullPointerException("sm: " + sm + " original: " + o);
+		if (o.pcGetStateManager() != null) 
+			throw new IllegalArgumentException(o + " is not transient");
+		_sm  = sm;
+		_original = o;
+	}
+	
+	/**
+	 * Gets the current state of the persistent instance.
+	 */
+	public Object getManagedObject() {
+		return _sm.getManagedInstance();
+	}
+	
+	/**
+	 * Gets the original state of the persistent instance as a transient instance.
+	 */
+	public Object getOriginalObject() {
+		return _original;
+	}
+	
+	/**
+	 * Gets the name of the updated fields.
+	 * 
+	 * @return persistent property names that are modified.
+	 * For deleted instances the array is empty and for newly created instances
+	 * the array contains all the fields.
+	 */
+	public String[] getUpdatedFields() {
+		BitSet dirty = _sm.getDirty();
+		String[] names = new String[dirty.cardinality()];
+		int j = 0;
+		for (int i = 0; i < dirty.size(); i++) {
+			if (dirty.get(i)) {
+				names[j++] = _sm.getMetaData().getField(i).getName();
+			}
+		}
+		return names;
+	}
+	
+	/**
+	 * Gets the type of this audit.
+	 */
+	public AuditableOperation getType() {
+		PCState state = _sm.getPCState();
+		if (state.isNew()) return AuditableOperation.CREATE;
+		if (state.isDeleted()) return AuditableOperation.DELETE;
+		if (state.isDirty()) return AuditableOperation.UPDATE;
+		return null; // should not happen
+	}
+}

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java?rev=1161349&r1=1161348&r2=1161349&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OpenJPASavepoint.java Thu Aug 25 01:06:39 2011
@@ -21,7 +21,6 @@ package org.apache.openjpa.kernel;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 
 /**
@@ -31,14 +30,14 @@ import java.util.Map;
  * @author Steve Kim
  * @since 0.3.4
  */
+@SuppressWarnings("serial")
 public class OpenJPASavepoint implements Serializable {
 
     private final Broker _broker;
     private final String _name;
     private final boolean _copy;
 
-    // <StateManagerImpl, SavepointFieldManager>
-    private Map _saved;
+     private Map<StateManagerImpl, SavepointFieldManager> _saved;
 
     /**
      * Constructor. Indicate whether to copy field data into memory.
@@ -73,7 +72,7 @@ public class OpenJPASavepoint implements
     /**
      * Return the map of states to savepoint data.
      */
-    protected Map getStates() {
+    protected Map<StateManagerImpl, SavepointFieldManager> getStates() {
         return _saved;
     }
 
@@ -81,14 +80,12 @@ public class OpenJPASavepoint implements
      * Set this savepoint, saving any state for the passed-in
      * {@link OpenJPAStateManager}s as necessary.
      */
-    public void save(Collection states) {
+    public void save(Collection<StateManagerImpl> states) {
         if (_saved != null)
             throw new IllegalStateException();
 
-        _saved = new HashMap((int) (states.size() * 1.33 + 1));
-        StateManagerImpl sm;
-        for (Iterator i = states.iterator(); i.hasNext();) {
-            sm = (StateManagerImpl) i.next();
+        _saved = new HashMap<StateManagerImpl, SavepointFieldManager>((int) (states.size() * 1.33 + 1));
+        for (StateManagerImpl sm : states) {
             _saved.put(sm, new SavepointFieldManager(sm, _copy));
         }
     }
@@ -110,16 +107,16 @@ public class OpenJPASavepoint implements
      *
      * @param previous previous savepoints set in the transaction
      */
-    public Collection rollback(Collection previous) {
-        Map saved;
+    public Collection<SavepointFieldManager> rollback(Collection<OpenJPASavepoint> previous) {
+        Map<StateManagerImpl, SavepointFieldManager> saved;
         if (previous.isEmpty())
             saved = _saved;
         else {
             // merge all changes into one collection, allowing for later
             // SavepointFieldManagers to replace previous ones.
-            saved = new HashMap();
-            for (Iterator i = previous.iterator(); i.hasNext();)
-                saved.putAll(((OpenJPASavepoint) i.next()).getStates());
+            saved = new HashMap<StateManagerImpl, SavepointFieldManager>();
+            for (OpenJPASavepoint savepoint : previous)
+                saved.putAll(savepoint.getStates());
             saved.putAll(_saved);
         }
         _saved = null;