blob: 6aa196bcbe7b0b7f292bcaaaf4a930e9d91a30fe [file] [log] [blame]
/*
* 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 loading 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 TransactionListener, RegisterClassListener {
private final 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, AuditCallback> _saved;
private final ReentrantLock _lock = new ReentrantLock();
public AuditManager(Auditor auditor) {
super();
if (auditor == null) {
throw new NullPointerException("null auditor");
}
setPreFlush(false);
_auditor = auditor;
_allTypes = new HashSet<>();
_newTypes = new HashSet<>();
_updateTypes = new HashSet<>();
_deleteTypes = new HashSet<>();
_saved = new ConcurrentHashMap<>();
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 Auditor getAuditor() {
return _auditor;
}
public Set<Class<?>> getAuditedTypes() {
return Collections.unmodifiableSet(_allTypes);
}
/**
* -----------------------------------------------------------------------
* Transaction callbacks.
* -----------------------------------------------------------------------
*/
@Override
public void afterBegin(TransactionEvent event) {
_lock.lock();
try {
Broker broker = (Broker)event.getSource();
AuditCallback cb = new AuditCallback(broker);
broker.addLifecycleListener(cb, _allTypes.toArray(new Class<?>[_allTypes.size()]));
_saved.put(broker, cb);
} finally {
_lock.unlock();
}
}
@Override
public void beforeCommit(TransactionEvent event) {
_lock.lock();
try {
AuditCallback cb = _saved.get(event.getSource());
if (cb != null) {
cb.audit();
}
} finally {
_lock.unlock();
}
}
private void forget(Broker broker) {
AuditCallback cb = _saved.remove(broker);
if (cb != null) {
broker.removeLifecycleListener(cb);
cb.clear();
}
}
@Override
public void afterCommit(TransactionEvent event) {
forget((Broker)event.getSource());
}
@Override
public void afterRollback(TransactionEvent event) {
forget((Broker)event.getSource());
}
@Override
public void afterCommitComplete(TransactionEvent event) {
forget((Broker)event.getSource());
}
@Override
public void afterRollbackComplete(TransactionEvent event) {
forget((Broker)event.getSource());
}
@Override
public void beforeFlush(TransactionEvent event) {
}
@Override
public void afterFlush(TransactionEvent event) {
}
@Override
public void afterStateTransitions(TransactionEvent 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;
}
/**
* 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;
}
/**
* Gets an implementation.
*/
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
*/
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));
}
/**
* Listens to entity life cycle operations and saves them for auditing.
*
*/
private class AuditCallback implements LifecycleListener {
private final Broker _broker;
private final Map<StateManagerImpl,PersistenceCapable> _audits =
new ConcurrentHashMap<>();
AuditCallback(Broker broker) {
_broker = broker;
}
void audit() {
if (_audits.isEmpty()) return;
Collection<Audited> news = new HashSet<>();
Collection<Audited> updates = new HashSet<>();
Collection<Audited> deletes = new HashSet<>();
for (Map.Entry<StateManagerImpl,PersistenceCapable> e : _audits.entrySet()) {
StateManagerImpl sm = e.getKey();
Audited audited = new Audited(sm, e.getValue());
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();
}
}
}
/**
* Saves the source of the given event for auditing.
* @param op an auditable operation
* @param event the event
*/
protected void save(AuditableOperation op, LifecycleEvent event) {
StateManagerImpl sm = getImpl(event.getSource());
if (sm != null && !_audits.containsKey(sm) && isAuditable(op, 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);
_audits.put(sm, copy);
}
}
void clear() {
_audits.clear();
}
/**
* 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) {
save(AuditableOperation.UPDATE, event);
}
@Override
public void beforePersist(LifecycleEvent event) {
}
@Override
public void afterRefresh(LifecycleEvent event) {
}
@Override
public void beforeStore(LifecycleEvent event) {
save(AuditableOperation.UPDATE, 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) {
save(AuditableOperation.UPDATE, 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) {
save(AuditableOperation.UPDATE, event);
}
}
}