mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#390 - ENH: Add Change log mechanism for easy fine grained logging of insert, update, delete activity
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks an entity bean as being included in the change logging.
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ChangeLog {
|
||||
|
||||
/**
|
||||
* Set this to true to exclude inserts on the associated bean type
|
||||
* from being included in the change log.
|
||||
*/
|
||||
boolean excludeInserts() default false;
|
||||
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbEncrypt;
|
||||
import com.avaje.ebean.event.*;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogRegister;
|
||||
import com.avaje.ebean.meta.MetaInfoManager;
|
||||
import com.avaje.ebean.util.ClassUtil;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
@@ -280,6 +283,12 @@ public class ServerConfig {
|
||||
private List<ServerConfigStartup> configStartupListeners = new ArrayList<ServerConfigStartup>();
|
||||
private List<TransactionEventListener> transactionEventListeners = new ArrayList<TransactionEventListener>();
|
||||
|
||||
private ChangeLogPrepare changeLogPrepare;
|
||||
|
||||
private ChangeLogListener changeLogListener;
|
||||
|
||||
private ChangeLogRegister changeLogRegister;
|
||||
|
||||
private EncryptKeyManager encryptKeyManager;
|
||||
|
||||
private EncryptDeployManager encryptDeployManager;
|
||||
@@ -636,6 +645,62 @@ public class ServerConfig {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ChangeLogPrepare.
|
||||
* <p>
|
||||
* This is used to set user context information to the ChangeSet in the
|
||||
* foreground thread prior to the logging occurring in a background thread.
|
||||
* </p>
|
||||
*/
|
||||
public ChangeLogPrepare getChangeLogPrepare() {
|
||||
return changeLogPrepare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChangeLogPrepare.
|
||||
* <p>
|
||||
* This is used to set user context information to the ChangeSet in the
|
||||
* foreground thread prior to the logging occurring in a background thread.
|
||||
* </p>
|
||||
*/
|
||||
public void setChangeLogPrepare(ChangeLogPrepare changeLogPrepare) {
|
||||
this.changeLogPrepare = changeLogPrepare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ChangeLogListener which actually performs the logging of change sets
|
||||
* in the background.
|
||||
*/
|
||||
public ChangeLogListener getChangeLogListener() {
|
||||
return changeLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChangeLogListener which actually performs the logging of change sets
|
||||
* in the background.
|
||||
*/
|
||||
public void setChangeLogListener(ChangeLogListener changeLogListener) {
|
||||
this.changeLogListener = changeLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ChangeLogRegister which controls which ChangeLogFilter is used for each
|
||||
* bean type and in this way provide fine grained control over which persist requests
|
||||
* are included in the change log.
|
||||
*/
|
||||
public ChangeLogRegister getChangeLogRegister() {
|
||||
return changeLogRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChangeLogRegister which controls which ChangeLogFilter is used for each
|
||||
* bean type and in this way provide fine grained control over which persist requests
|
||||
* are included in the change log.
|
||||
*/
|
||||
public void setChangeLogRegister(ChangeLogRegister changeLogRegister) {
|
||||
this.changeLogRegister = changeLogRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB migration configuration.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
import com.avaje.ebean.ValuePair;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A bean insert, update or delete change sent as part of a ChangeSet.
|
||||
*/
|
||||
public class BeanChange {
|
||||
|
||||
/**
|
||||
* The underling base table name.
|
||||
*/
|
||||
String table;
|
||||
|
||||
/**
|
||||
* The id value.
|
||||
*/
|
||||
Object id;
|
||||
|
||||
/**
|
||||
* The INSERT, UPDATE or DELETE change type.
|
||||
*/
|
||||
ChangeType type;
|
||||
|
||||
/**
|
||||
* The time the bean change was created.
|
||||
*/
|
||||
long eventTime;
|
||||
|
||||
/**
|
||||
* The values for insert or update. Note that null values are not included for insert.
|
||||
*/
|
||||
Map<String, ValuePair> values;
|
||||
|
||||
/**
|
||||
* Construct with all the details.
|
||||
*/
|
||||
public BeanChange(String table, Object id, ChangeType type, Map<String, ValuePair> values) {
|
||||
this.table = table;
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.eventTime = System.currentTimeMillis();
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor for JSON tools.
|
||||
*/
|
||||
public BeanChange() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object type (typically table name).
|
||||
*/
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object type (for JSON tools).
|
||||
*/
|
||||
public void setTable(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object id.
|
||||
*/
|
||||
public Object getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean id (for JSON tools).
|
||||
*/
|
||||
public void setId(Object id) {
|
||||
this.id = this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the change type (INSERT, UPDATE or DELETE).
|
||||
*/
|
||||
public ChangeType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type (for JSON tools).
|
||||
*/
|
||||
public void setType(ChangeType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the event time in epoch millis.
|
||||
*/
|
||||
public long getEventTime() {
|
||||
return eventTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event time in epoch millis.
|
||||
*/
|
||||
public void setEventTime(long eventTime) {
|
||||
this.eventTime = eventTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value pairs. For inserts the ValuePair oldValue is always null.
|
||||
*/
|
||||
public Map<String, ValuePair> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value pairs (for JSON tools).
|
||||
*/
|
||||
public void setValues(Map<String, ValuePair> values) {
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
import com.avaje.ebean.event.BeanPersistRequest;
|
||||
|
||||
/**
|
||||
* Used to provide fine grained control over what persist requests are included in the change log.
|
||||
*/
|
||||
public interface ChangeLogFilter {
|
||||
|
||||
/**
|
||||
* Return true if this insert request should be included in the change log.
|
||||
*/
|
||||
boolean includeInsert(BeanPersistRequest<?> insertRequest);
|
||||
|
||||
/**
|
||||
* Return true if this update request should be included in the change log.
|
||||
*/
|
||||
boolean includeUpdate(BeanPersistRequest<?> updateRequest);
|
||||
|
||||
/**
|
||||
* Return true if this delete request should be included in the change log.
|
||||
*/
|
||||
boolean includeDelete(BeanPersistRequest<?> deleteRequest);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
/**
|
||||
* Listen for changes.
|
||||
* <p>
|
||||
* Implementations can take the changes and store them in a document store for auditing purposes etc.
|
||||
* </p>
|
||||
*/
|
||||
public interface ChangeLogListener {
|
||||
|
||||
/**
|
||||
* Log the batch of changes.
|
||||
* <p>
|
||||
* For small transactions this will be all the changes in the transaction.
|
||||
* For larger/longer transactions this can be a 'batch' of changes made and the actual transaction
|
||||
* has not yet committed or rolled back and a later change set will contain the final changeSet for
|
||||
* the transaction with it's final status of <code>COMMITTED</code> or <code>ROLLBACK</code>.
|
||||
*/
|
||||
void log(ChangeSet changeSet);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
/**
|
||||
* Listen for changes.
|
||||
* <p>
|
||||
* Implementations can take the changes and store them in a document store for auditing purposes etc.
|
||||
* </p>
|
||||
*/
|
||||
public interface ChangeLogPrepare {
|
||||
|
||||
/**
|
||||
* In the foreground prepare the changeLog for sending.
|
||||
* <p>
|
||||
* This is intended to set extra context information onto the ChangeSet such
|
||||
* as the application user id and client ip address.
|
||||
* </p>
|
||||
* <p>
|
||||
* Returning false means the changeLog is not sent to the log() method in a background thread
|
||||
* and implies that the changeSet should be ignored or that is has been handled in this prepare()
|
||||
* method call.
|
||||
* </p>
|
||||
*
|
||||
* @return true if the changeLog should then be sent to the log method in a background thread.
|
||||
*/
|
||||
boolean prepare(ChangeSet changeSet);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
/**
|
||||
* Used to assign ChangeLogFilters to bean types.
|
||||
* <p>
|
||||
* Ebean has a built in implementation that uses the ChangeLog annotation to build
|
||||
* appropriate ChangeLogFilters but you can provide an implementation to use instead.
|
||||
* </p>
|
||||
*/
|
||||
public interface ChangeLogRegister {
|
||||
|
||||
/**
|
||||
* For the given bean type return the Change log filter to use.
|
||||
* <p>
|
||||
* This filter provides control over which persist request are included in the change log.
|
||||
*/
|
||||
ChangeLogFilter getChangeFilter(Class<?> beanType);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a set of changes.
|
||||
*/
|
||||
public class ChangeSet {
|
||||
|
||||
/**
|
||||
* A UUID transaction id specifically created for the change set.
|
||||
*/
|
||||
String txnId;
|
||||
|
||||
/**
|
||||
* For large transactions with many change sets this is an incrementing counter.
|
||||
*/
|
||||
long txnBatch;
|
||||
|
||||
/**
|
||||
* The state of the transaction (change sets can be sent prior to commit or rollback
|
||||
* with large transactions).
|
||||
*/
|
||||
TxnState txnState;
|
||||
|
||||
/**
|
||||
* User defined 'source' such as the application name.
|
||||
*/
|
||||
String source;
|
||||
|
||||
/**
|
||||
* Application user id expected to be optionally populated by ChangeLogPrepare.
|
||||
*/
|
||||
String userId;
|
||||
|
||||
/**
|
||||
* Application user ip address expected to be optionally populated by ChangeLogPrepare.
|
||||
*/
|
||||
String userIpAddress;
|
||||
|
||||
/**
|
||||
* Arbitrary user context information expected to be optionally populated by ChangeLogPrepare.
|
||||
*/
|
||||
String userContext;
|
||||
|
||||
/**
|
||||
* The bean changes.
|
||||
*/
|
||||
List<BeanChange> changes = new ArrayList<BeanChange>();
|
||||
|
||||
/**
|
||||
* Construct with a txnId.
|
||||
*/
|
||||
public ChangeSet(String txnId, long txnBatch) {
|
||||
this.txnId = txnId;
|
||||
this.txnBatch = txnBatch;
|
||||
this.txnState = TxnState.IN_PROGRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor for JSON tools.
|
||||
*/
|
||||
public ChangeSet() {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "txnId:" + txnId + " txnState:" + txnState + " txnBatch:" + txnBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bean change to the change set.
|
||||
*/
|
||||
public void addBeanChange(BeanChange beanChange) {
|
||||
changes.add(beanChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the txnId.
|
||||
*/
|
||||
public String getTxnId() {
|
||||
return txnId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the txnId (used by JSON tools).
|
||||
*/
|
||||
public void setTxnId(String txnId) {
|
||||
this.txnId = txnId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch id.
|
||||
*/
|
||||
public long getTxnBatch() {
|
||||
return txnBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the batch id (used by JSON tools).
|
||||
*/
|
||||
public void setTxnBatch(long txnBatch) {
|
||||
this.txnBatch = txnBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction state. This will be IN_PROGRESS for many changeSets in large transactions
|
||||
* as the changeSets are sent in batches before the transaction has completed.
|
||||
*/
|
||||
public TxnState getTxnState() {
|
||||
return txnState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state (used by JSON tools).
|
||||
*/
|
||||
public void setTxnState(TxnState txnState) {
|
||||
this.txnState = txnState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a code that identifies the source of the change (like the name of the application).
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source of the change (like the name of the application).
|
||||
*/
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application user Id.
|
||||
*/
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application user Id.
|
||||
* <p>
|
||||
* This can be set by the ChangeLogListener in the prepare() method which is called
|
||||
* in the foreground thread.
|
||||
* </p>
|
||||
*/
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application users ip address.
|
||||
*/
|
||||
public String getUserIpAddress() {
|
||||
return userIpAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application users ip address.
|
||||
* <p>
|
||||
* This can be set by the ChangeLogListener in the prepare() method which is called
|
||||
* in the foreground thread.
|
||||
* </p>
|
||||
*/
|
||||
public void setUserIpAddress(String userIpAddress) {
|
||||
this.userIpAddress = userIpAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a user context value - anything you set yourself in ChangeLogListener prepare().
|
||||
*/
|
||||
public String getUserContext() {
|
||||
return userContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a user context value (anything you like).
|
||||
* <p>
|
||||
* This can be set by the ChangeLogListener in the prepare() method which is called
|
||||
* in the foreground thread.
|
||||
* </p>
|
||||
*/
|
||||
public void setUserContext(String userContext) {
|
||||
this.userContext = userContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean changes.
|
||||
*/
|
||||
public List<BeanChange> getChanges() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean changes (used by JSON tools).
|
||||
*/
|
||||
public void setChanges(List<BeanChange> changes) {
|
||||
this.changes = changes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
/**
|
||||
* The type of the change.
|
||||
*/
|
||||
public enum ChangeType {
|
||||
|
||||
/**
|
||||
* The change was an insert.
|
||||
*/
|
||||
INSERT("I"),
|
||||
|
||||
/**
|
||||
* The change was an update.
|
||||
*/
|
||||
UPDATE("U"),
|
||||
|
||||
/**
|
||||
* The change was a delete.
|
||||
*/
|
||||
DELETE("D");
|
||||
|
||||
final String code;
|
||||
|
||||
ChangeType(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.avaje.ebean.event.changelog;
|
||||
|
||||
/**
|
||||
* Transaction state when ChangeSets are sent to the ChangeSetListener.
|
||||
* <p>
|
||||
* For large transactions multiple ChangeSets can be sent in a batch fashion and in this
|
||||
* case all but the last changeSet with have IN_PROGRESS state and the last changeSet will
|
||||
* have the COMMITTED or ROLLBACK state.
|
||||
* </p>
|
||||
*/
|
||||
public enum TxnState {
|
||||
|
||||
/**
|
||||
* The Transaction is still in progress.
|
||||
* <p>
|
||||
* Used when the transaction is large/long and Ebean wants to send out the changeSets
|
||||
* in batches and a changeSet is send before the transaction has completed.
|
||||
*/
|
||||
IN_PROGRESS("I"),
|
||||
|
||||
/**
|
||||
* The Transaction was committed.
|
||||
*/
|
||||
COMMITTED("C"),
|
||||
|
||||
/**
|
||||
* The Transaction was rolled back.
|
||||
*/
|
||||
ROLLBACK("R");
|
||||
|
||||
private final String code;
|
||||
|
||||
TxnState(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -201,4 +201,15 @@ public interface JsonContext {
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
JsonParser createParser(Reader reader) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Return a helper that can write scalar types known to Ebean to Jackson.
|
||||
* <p>
|
||||
* Ebean has built in support for java8 and Joda types as well as the other
|
||||
* standard JDK types like URI, URL, UUID etc. This is a fast simple way to
|
||||
* write any of those types to Jackson.
|
||||
* </p>
|
||||
*/
|
||||
JsonScalar getScalar(JsonGenerator generator);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.avaje.ebean.text.json;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Writes any scalar type known to Ebean the Jackson generator.
|
||||
*/
|
||||
public interface JsonScalar {
|
||||
|
||||
void write(String name, Object value) throws IOException;
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.avaje.ebeaninternal.api;
|
||||
import com.avaje.ebean.TransactionCallback;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.config.PersistBatch;
|
||||
import com.avaje.ebean.event.changelog.BeanChange;
|
||||
import com.avaje.ebean.event.changelog.ChangeSet;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequest;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchControl;
|
||||
@@ -87,6 +89,16 @@ public class ScopedTransaction implements SpiTransaction {
|
||||
transaction.logSummary(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBeanChange(BeanChange beanChange) {
|
||||
transaction.addBeanChange(beanChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChangeLog(ChangeSet changes) {
|
||||
transaction.sendChangeLog(changes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDerivedRelationship(DerivedRelationshipData assocBean) {
|
||||
transaction.registerDerivedRelationship(assocBean);
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.List;
|
||||
* Service Provider extension to EbeanServer.
|
||||
*/
|
||||
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
|
||||
|
||||
/**
|
||||
* For internal use, shutdown of the server invoked by JVM Shutdown.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.List;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.event.changelog.BeanChange;
|
||||
import com.avaje.ebean.event.changelog.ChangeSet;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequest;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchControl;
|
||||
@@ -233,4 +235,13 @@ public interface SpiTransaction extends Transaction {
|
||||
*/
|
||||
void flushBatchOnCollection();
|
||||
|
||||
/**
|
||||
* Add a bean change to the change log.
|
||||
*/
|
||||
void addBeanChange(BeanChange beanChange);
|
||||
|
||||
/**
|
||||
* Send the change set to be prepared and then logged.
|
||||
*/
|
||||
void sendChangeLog(ChangeSet changeSet);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.avaje.ebean.event.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.CompoundType;
|
||||
import com.avaje.ebean.config.ScalarTypeConverter;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.event.BeanFindController;
|
||||
import com.avaje.ebean.event.BeanPersistController;
|
||||
import com.avaje.ebean.event.BeanPersistListener;
|
||||
import com.avaje.ebean.event.BeanPostLoad;
|
||||
import com.avaje.ebean.event.BeanQueryAdapter;
|
||||
import com.avaje.ebean.event.ServerConfigStartup;
|
||||
import com.avaje.ebean.event.TransactionEventListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogRegister;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interesting classes for a EbeanServer such as Embeddable, Entity,
|
||||
@@ -58,6 +66,14 @@ public class BootupClasses implements ClassPathSearchMatcher {
|
||||
private final List<BeanQueryAdapter> queryAdapterInstances = new ArrayList<BeanQueryAdapter>();
|
||||
private final List<TransactionEventListener> transactionEventListenerInstances = new ArrayList<TransactionEventListener>();
|
||||
|
||||
private Class<?> changeLogPrepareClass;
|
||||
private Class<?> changeLogListenerClass;
|
||||
private Class<?> changeLogRegisterClass;
|
||||
|
||||
private ChangeLogPrepare changeLogPrepare;
|
||||
private ChangeLogListener changeLogListener;
|
||||
private ChangeLogRegister changeLogRegister;
|
||||
|
||||
public BootupClasses() {
|
||||
}
|
||||
|
||||
@@ -175,98 +191,122 @@ public class BootupClasses implements ClassPathSearchMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
public List<BeanQueryAdapter> getBeanQueryAdapters() {
|
||||
// add class registered BeanQueryAdapter to the
|
||||
// already created instances
|
||||
for (Class<?> cls : beanQueryAdapterList) {
|
||||
try {
|
||||
BeanQueryAdapter newInstance = (BeanQueryAdapter) cls.newInstance();
|
||||
queryAdapterInstances.add(newInstance);
|
||||
} catch (Exception e) {
|
||||
String msg = "Error creating BeanQueryAdapter " + cls;
|
||||
logger.error(msg, e);
|
||||
public void addChangeLogInstances(ServerConfig serverConfig) {
|
||||
changeLogListener = serverConfig.getChangeLogListener();
|
||||
changeLogRegister = serverConfig.getChangeLogRegister();
|
||||
changeLogPrepare = serverConfig.getChangeLogPrepare();
|
||||
|
||||
// if not already set create the implementations found
|
||||
// via classpath scanning
|
||||
if (changeLogPrepare == null && changeLogPrepareClass != null) {
|
||||
changeLogPrepare = (ChangeLogPrepare)create(changeLogPrepareClass, false);
|
||||
}
|
||||
if (changeLogListener == null && changeLogListenerClass != null) {
|
||||
changeLogListener = (ChangeLogListener)create(changeLogListenerClass, false);
|
||||
}
|
||||
if (changeLogRegister == null && changeLogRegisterClass != null) {
|
||||
changeLogRegister = (ChangeLogRegister)create(changeLogRegisterClass, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance using the default constructor returning null if there
|
||||
* is no default constructor (implying the class was not intended to be instantiated
|
||||
* automatically via classpath scanning.
|
||||
* <p>
|
||||
* Use logOnException = true to log the error and carry on.
|
||||
*/
|
||||
private Object create(Class<?> cls, boolean logOnException) {
|
||||
try {
|
||||
// instantiate via found class
|
||||
Constructor constructor = cls.getConstructor();
|
||||
return constructor.newInstance();
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
logger.debug("Ignore/expected - no default constructor", e);
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (logOnException) {
|
||||
// not expected but we log and carry on
|
||||
logger.error("Error creating " + cls, e);
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// ok, stop the bus
|
||||
throw new IllegalStateException("Error creating " + cls, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the instance if it has a default constructor and add it to the list of instances.
|
||||
*/
|
||||
@SuppressWarnings(value = "unchecked")
|
||||
private <T> void createAdd(Class<?> cls, List<T> instances) {
|
||||
Object newInstance = create(cls, true);
|
||||
if (newInstance != null) {
|
||||
instances.add((T)newInstance);
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeLogPrepare getChangeLogPrepare() {
|
||||
return changeLogPrepare;
|
||||
}
|
||||
|
||||
public ChangeLogListener getChangeLogListener() {
|
||||
return changeLogListener;
|
||||
}
|
||||
|
||||
public ChangeLogRegister getChangeLogRegister() {
|
||||
return changeLogRegister;
|
||||
}
|
||||
|
||||
public List<BeanQueryAdapter> getBeanQueryAdapters() {
|
||||
// add class registered BeanQueryAdapter to the already created instances
|
||||
for (Class<?> cls : beanQueryAdapterList) {
|
||||
createAdd(cls, queryAdapterInstances);
|
||||
}
|
||||
return queryAdapterInstances;
|
||||
}
|
||||
|
||||
public List<BeanFindController> getBeanFindControllers() {
|
||||
// add class registered BeanFindController to the
|
||||
// list of created instances
|
||||
// add class registered BeanFindController to the list of created instances
|
||||
for (Class<?> cls : beanFindControllerList) {
|
||||
try {
|
||||
BeanFindController newInstance = (BeanFindController) cls.newInstance();
|
||||
findControllerInstances.add(newInstance);
|
||||
} catch (Exception e) {
|
||||
String msg = "Error creating BeanPersistController " + cls;
|
||||
logger.error(msg, e);
|
||||
}
|
||||
createAdd(cls, findControllerInstances);
|
||||
}
|
||||
|
||||
return findControllerInstances;
|
||||
}
|
||||
|
||||
public List<BeanPersistListener> getBeanPersistListeners() {
|
||||
// add class registered BeanPersistController to the
|
||||
// already created instances
|
||||
// add class registered BeanPersistController to the already created instances
|
||||
for (Class<?> cls : beanListenerList) {
|
||||
try {
|
||||
BeanPersistListener newInstance = (BeanPersistListener) cls.newInstance();
|
||||
persistListenerInstances.add(newInstance);
|
||||
} catch (Exception e) {
|
||||
String msg = "Error creating BeanPersistController " + cls;
|
||||
logger.error(msg, e);
|
||||
}
|
||||
createAdd(cls, persistListenerInstances);
|
||||
}
|
||||
|
||||
return persistListenerInstances;
|
||||
}
|
||||
|
||||
public List<BeanPersistController> getBeanPersistControllers() {
|
||||
// add class registered BeanPersistController to the
|
||||
// already created instances
|
||||
// add class registered BeanPersistController to the already created instances
|
||||
for (Class<?> cls : beanControllerList) {
|
||||
try {
|
||||
BeanPersistController newInstance = (BeanPersistController) cls.newInstance();
|
||||
persistControllerInstances.add(newInstance);
|
||||
} catch (Exception e) {
|
||||
String msg = "Error creating BeanPersistController " + cls;
|
||||
logger.error(msg, e);
|
||||
}
|
||||
createAdd(cls, persistControllerInstances);
|
||||
}
|
||||
|
||||
return persistControllerInstances;
|
||||
}
|
||||
|
||||
public List<BeanPostLoad> getBeanPostLoaders() {
|
||||
// add class registered BeanPostLoad to the already created instances
|
||||
for (Class<?> cls : beanPostLoadList) {
|
||||
try {
|
||||
BeanPostLoad newInstance = (BeanPostLoad) cls.newInstance();
|
||||
beanPostLoadInstances.add(newInstance);
|
||||
} catch (Exception e) {
|
||||
String msg = "Error creating BeanPersistController " + cls;
|
||||
logger.error(msg, e);
|
||||
}
|
||||
createAdd(cls, beanPostLoadInstances);
|
||||
}
|
||||
|
||||
return beanPostLoadInstances;
|
||||
}
|
||||
|
||||
public List<TransactionEventListener> getTransactionEventListeners() {
|
||||
// add class registered TransactionEventListener to the
|
||||
// already created instances
|
||||
// add class registered TransactionEventListener to the already created instances
|
||||
for (Class<?> cls : transactionEventListenerList) {
|
||||
try {
|
||||
TransactionEventListener newInstance = (TransactionEventListener) cls.newInstance();
|
||||
transactionEventListenerInstances.add(newInstance);
|
||||
} catch (Exception e) {
|
||||
String msg = "Error creating TransactionEventListener " + cls;
|
||||
logger.error(msg, e);
|
||||
}
|
||||
createAdd(cls, transactionEventListenerInstances);
|
||||
}
|
||||
|
||||
return transactionEventListenerInstances;
|
||||
}
|
||||
|
||||
@@ -385,6 +425,21 @@ public class BootupClasses implements ClassPathSearchMatcher {
|
||||
interesting = true;
|
||||
}
|
||||
|
||||
if (ChangeLogListener.class.isAssignableFrom(cls)) {
|
||||
changeLogListenerClass = cls;
|
||||
interesting = true;
|
||||
}
|
||||
|
||||
if (ChangeLogRegister.class.isAssignableFrom(cls)) {
|
||||
changeLogRegisterClass = cls;
|
||||
interesting = true;
|
||||
}
|
||||
|
||||
if (ChangeLogPrepare.class.isAssignableFrom(cls)) {
|
||||
changeLogPrepareClass = cls;
|
||||
interesting = true;
|
||||
}
|
||||
|
||||
return interesting;
|
||||
}
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
bootupClasses.addPersistListeners(serverConfig.getPersistListeners());
|
||||
bootupClasses.addQueryAdapters(serverConfig.getQueryAdapters());
|
||||
bootupClasses.addServerConfigStartup(serverConfig.getServerConfigStartupListeners());
|
||||
bootupClasses.addChangeLogInstances(serverConfig);
|
||||
|
||||
// run any ServerConfigStartup instances
|
||||
bootupClasses.runServerConfigStartup(serverConfig);
|
||||
|
||||
@@ -204,7 +204,7 @@ public final class DefaultServer implements SpiEbeanServer {
|
||||
this.serverCacheManager = cache;
|
||||
this.databasePlatform = config.getDatabasePlatform();
|
||||
this.backgroundExecutor = config.getBackgroundExecutor();
|
||||
|
||||
|
||||
this.serverName = serverConfig.getName();
|
||||
this.diffHelp = new DiffHelp(serverConfig.isDiffFlatMode());
|
||||
this.lazyLoadBatchSize = serverConfig.getLazyLoadBatchSize();
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebean.ValuePair;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper to perform a 'diff' for an insert.
|
||||
* <p>
|
||||
* This intentionally does not include any OneToMany or ManyToMany properties.
|
||||
* </p>
|
||||
*/
|
||||
public class DiffHelpInsert {
|
||||
|
||||
private DiffHelpInsert() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of the differences between a and b.
|
||||
* <p>
|
||||
* A and B must be of the same type. B can be null, in which case the 'dirty
|
||||
* diff' of a is returned.
|
||||
* </p>
|
||||
* <p>
|
||||
* This intentionally does not include as OneToMany or ManyToMany properties.
|
||||
* </p>
|
||||
*/
|
||||
public static Map<String, ValuePair> diff(EntityBean newBean, BeanDescriptor<?> desc) {
|
||||
|
||||
Map<String, ValuePair> map = new LinkedHashMap<String, ValuePair>();
|
||||
diff(null, map, newBean, desc);
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, BeanDescriptor<?> desc) {
|
||||
|
||||
// check the simple properties
|
||||
BeanProperty[] base = desc.propertiesBaseScalar();
|
||||
for (int i = 0; i < base.length; i++) {
|
||||
Object newVal = (newBean == null) ? null : base[i].getValue(newBean);
|
||||
if (newVal != null) {
|
||||
String propName = (prefix == null) ? base[i].getName() : prefix + base[i].getName();
|
||||
map.put(propName, new ValuePair(newVal, null));
|
||||
}
|
||||
}
|
||||
|
||||
diffAssocOne(prefix, newBean, desc, map);
|
||||
diffEmbedded(prefix, newBean, desc, map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the Embedded bean properties for differences.
|
||||
* <p>
|
||||
* If ANY of the properties are different then the whole Embedded bean is
|
||||
* determined to be different as is added to the map.
|
||||
* </p>
|
||||
*/
|
||||
private static void diffEmbedded(String prefix, EntityBean newBean, BeanDescriptor<?> desc, Map<String, ValuePair> map) {
|
||||
|
||||
BeanPropertyAssocOne<?>[] emb = desc.propertiesEmbedded();
|
||||
|
||||
for (int i = 0; i < emb.length; i++) {
|
||||
EntityBean newVal = (EntityBean) emb[i].getValue(newBean);
|
||||
|
||||
if (newVal != null) {
|
||||
String propName = (prefix == null) ? emb[i].getName() : prefix + emb[i].getName();
|
||||
|
||||
BeanDescriptor<?> embDesc = emb[i].getTargetDescriptor();
|
||||
diff(propName + ".", map, newVal, embDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the properties are different by null OR if the id value is different,
|
||||
* then add the Assoc One bean to the map.
|
||||
*/
|
||||
private static void diffAssocOne(String prefix, EntityBean newBean, BeanDescriptor<?> desc, Map<String, ValuePair> map) {
|
||||
|
||||
BeanPropertyAssocOne<?>[] ones = desc.propertiesOne();
|
||||
|
||||
for (int i = 0; i < ones.length; i++) {
|
||||
Object newVal = ones[i].getValue(newBean);
|
||||
|
||||
if (newVal != null) {
|
||||
BeanDescriptor<?> oneDesc = ones[i].getTargetDescriptor();
|
||||
Object newId = oneDesc.getId((EntityBean)newVal);
|
||||
if (newId != null) {
|
||||
String propName = (prefix == null) ? ones[i].getName() : prefix + ones[i].getName();
|
||||
String idName = oneDesc.getIdProperty().getName();
|
||||
map.put(propName + "." + idName, new ValuePair(newId, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -176,7 +176,7 @@ public class InternalConfiguration {
|
||||
|
||||
public JsonContext createJsonContext(SpiEbeanServer server) {
|
||||
|
||||
return new DJsonContext(server, jsonFactory);
|
||||
return new DJsonContext(server, jsonFactory, typeManager);
|
||||
}
|
||||
|
||||
public XmlConfig getXmlConfig() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebean.event.BeanPersistController;
|
||||
import com.avaje.ebean.event.BeanPersistListener;
|
||||
import com.avaje.ebean.event.BeanPersistRequest;
|
||||
import com.avaje.ebean.event.changelog.BeanChange;
|
||||
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
@@ -529,11 +530,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
beanDescriptor.cacheHandleDelete(idValue, this);
|
||||
}
|
||||
|
||||
private void changeLog() {
|
||||
BeanChange changeLogBean = beanDescriptor.getChangeLogBean(this);
|
||||
if (changeLogBean != null) {
|
||||
transaction.addBeanChange(changeLogBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post processing.
|
||||
*/
|
||||
public void postExecute() {
|
||||
|
||||
changeLog();
|
||||
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebean.SqlUpdate;
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.ValuePair;
|
||||
import com.avaje.ebean.annotation.ConcurrencyMode;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
@@ -15,6 +16,9 @@ import com.avaje.ebean.event.BeanPersistController;
|
||||
import com.avaje.ebean.event.BeanPersistListener;
|
||||
import com.avaje.ebean.event.BeanPostLoad;
|
||||
import com.avaje.ebean.event.BeanQueryAdapter;
|
||||
import com.avaje.ebean.event.changelog.BeanChange;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogFilter;
|
||||
import com.avaje.ebean.event.changelog.ChangeType;
|
||||
import com.avaje.ebean.meta.MetaBeanInfo;
|
||||
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlan;
|
||||
@@ -26,7 +30,9 @@ import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
|
||||
import com.avaje.ebeaninternal.server.core.CacheOptions;
|
||||
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import com.avaje.ebeaninternal.server.core.DiffHelpInsert;
|
||||
import com.avaje.ebeaninternal.server.core.InternString;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequest;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
@@ -175,6 +181,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
*/
|
||||
private final BeanFindController beanFinder;
|
||||
|
||||
/**
|
||||
* Used for fine grain filtering for the change log.
|
||||
*/
|
||||
private final ChangeLogFilter changeLogFilter;
|
||||
|
||||
/**
|
||||
* The table joins for this bean.
|
||||
*/
|
||||
@@ -337,6 +348,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
this.persistListener = deploy.getPersistListener();
|
||||
this.beanPostLoad = deploy.getPostLoad();
|
||||
this.queryAdapter = deploy.getQueryAdapter();
|
||||
this.changeLogFilter = deploy.getChangeLogFilter();
|
||||
|
||||
this.defaultSelectClause = deploy.getDefaultSelectClause();
|
||||
this.defaultSelectClauseSet = deploy.parseDefaultSelectClause(defaultSelectClause);
|
||||
@@ -421,7 +433,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
this.unloadProperties = derivePropertiesToUnload(prototypeEntityBean);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Derive an array of property positions for properties that are initialised in the constructor.
|
||||
* These properties need to be unloaded when populating beans for queries.
|
||||
@@ -598,6 +610,60 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request should be included in the change log.
|
||||
*/
|
||||
public BeanChange getChangeLogBean(PersistRequestBean<T> request) {
|
||||
|
||||
if (changeLogFilter == null) {
|
||||
return null;
|
||||
}
|
||||
PersistRequest.Type type = request.getType();
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
return changeLogFilter.includeInsert(request) ? insertBeanChange(request): null;
|
||||
case UPDATE:
|
||||
return changeLogFilter.includeUpdate(request) ? updateBeanChange(request): null;
|
||||
case DELETE:
|
||||
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) :null;
|
||||
default:
|
||||
throw new IllegalStateException("Unhandled request type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean change for a delete.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private BeanChange deleteBeanChange(PersistRequestBean<T> request) {
|
||||
return newBeanChange(request.getBeanId(), ChangeType.DELETE, Collections.EMPTY_MAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean change for an update.
|
||||
*/
|
||||
private BeanChange updateBeanChange(PersistRequestBean<T> request) {
|
||||
return newBeanChange(request.getBeanId(), ChangeType.UPDATE, request.getEntityBeanIntercept().getDirtyValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean change for an insert.
|
||||
*/
|
||||
private BeanChange insertBeanChange(PersistRequestBean<T> request) {
|
||||
return newBeanChange(request.getBeanId(), ChangeType.INSERT, insertDiff(request.getEntityBean()));
|
||||
}
|
||||
|
||||
private BeanChange newBeanChange(Object id, ChangeType changeType, Map<String, ValuePair> values) {
|
||||
return new BeanChange(getBaseTable(), id, changeType, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* For insert we create a Map of ValuePair to have the same structure as update.
|
||||
*/
|
||||
private Map<String, ValuePair> insertDiff(EntityBean entityBean) {
|
||||
return DiffHelpInsert.diff(entityBean, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the cache once the server has started.
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,10 @@ import com.avaje.ebean.config.dbplatform.DbHistorySupport;
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.IdGenerator;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogFilter;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogRegister;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
@@ -105,6 +109,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final BeanManagerFactory beanManagerFactory;
|
||||
|
||||
private final ChangeLogListener changeLogListener;
|
||||
|
||||
private final ChangeLogRegister changeLogRegister;
|
||||
|
||||
private final ChangeLogPrepare changeLogPrepare;
|
||||
|
||||
private int enhancedClassCount;
|
||||
|
||||
private final boolean updateChangesOnly;
|
||||
@@ -199,6 +209,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
this.reflectFactory = createReflectionFactory();
|
||||
this.transientProperties = new TransientProperties();
|
||||
this.changeLogPrepare = bootupClasses.getChangeLogPrepare();
|
||||
this.changeLogListener = bootupClasses.getChangeLogListener();
|
||||
this.changeLogRegister = bootupClasses.getChangeLogRegister();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1003,6 +1016,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
persistListenerManager.addPersistListeners(descriptor);
|
||||
beanQueryAdapterManager.addQueryAdapter(descriptor);
|
||||
beanFinderManager.addFindControllers(descriptor);
|
||||
|
||||
if (changeLogRegister != null) {
|
||||
ChangeLogFilter changeFilter = changeLogRegister.getChangeFilter(descriptor.getBeanType());
|
||||
if (changeFilter != null) {
|
||||
descriptor.setChangeLogFilter(changeFilter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1451,7 +1472,22 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the changeLogPrepare (for setting user context into the ChangeSet
|
||||
* in the foreground thread).
|
||||
*/
|
||||
public ChangeLogPrepare getChangeLogPrepare() {
|
||||
return changeLogPrepare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the changeLogListener (that actually does the logging).
|
||||
*/
|
||||
public ChangeLogListener getChangeLogListener() {
|
||||
return changeLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator to sort the BeanDescriptors by name.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.avaje.ebean.event.BeanPersistController;
|
||||
import com.avaje.ebean.event.BeanPersistListener;
|
||||
import com.avaje.ebean.event.BeanPostLoad;
|
||||
import com.avaje.ebean.event.BeanQueryAdapter;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogFilter;
|
||||
import com.avaje.ebeaninternal.server.core.CacheOptions;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
|
||||
import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistController;
|
||||
@@ -154,6 +155,8 @@ public class DeployBeanDescriptor<T> {
|
||||
|
||||
private boolean processedRawSqlExtend;
|
||||
|
||||
private ChangeLogFilter changeLogFilter;
|
||||
|
||||
/**
|
||||
* Construct the BeanDescriptor.
|
||||
*/
|
||||
@@ -305,6 +308,14 @@ public class DeployBeanDescriptor<T> {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
public void setChangeLogFilter(ChangeLogFilter changeLogFilter) {
|
||||
this.changeLogFilter = changeLogFilter;
|
||||
}
|
||||
|
||||
public ChangeLogFilter getChangeLogFilter() {
|
||||
return changeLogFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Inheritance mapping information. This will be null if this type
|
||||
* of bean is not involved in any ORM inheritance mapping.
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.avaje.ebean.text.json.*;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.type.TypeManager;
|
||||
import com.avaje.ebeaninternal.util.ParamTypeHelper;
|
||||
import com.avaje.ebeaninternal.util.ParamTypeHelper.ManyType;
|
||||
import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
|
||||
@@ -25,17 +26,24 @@ public class DJsonContext implements JsonContext {
|
||||
|
||||
private final JsonFactory jsonFactory;
|
||||
|
||||
private final TypeManager typeManager;
|
||||
|
||||
private final Object defaultObjectMapper;
|
||||
|
||||
private final JsonConfig.Include defaultInclude;
|
||||
|
||||
public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory) {
|
||||
public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory, TypeManager typeManager) {
|
||||
this.server = server;
|
||||
this.typeManager = typeManager;
|
||||
this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory();
|
||||
this.defaultObjectMapper = this.server.getServerConfig().getObjectMapper();
|
||||
this.defaultInclude = this.server.getServerConfig().getJsonInclude();
|
||||
}
|
||||
|
||||
public JsonScalar getScalar(JsonGenerator generator) {
|
||||
return new DefaultJsonScalar(typeManager, new WriteJson(generator, defaultInclude));
|
||||
}
|
||||
|
||||
public boolean isSupportedType(Type genericType) {
|
||||
return server.isSupportedType(genericType);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.avaje.ebeaninternal.server.text.json;
|
||||
|
||||
import com.avaje.ebean.text.json.JsonScalar;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.TypeManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Default implementation of JsonScalar.
|
||||
*/
|
||||
public class DefaultJsonScalar implements JsonScalar {
|
||||
|
||||
|
||||
private final TypeManager typeManager;
|
||||
|
||||
private final WriteJson writeJson;
|
||||
|
||||
public DefaultJsonScalar(TypeManager typeManager, WriteJson writeJson) {
|
||||
this.typeManager = typeManager;
|
||||
this.writeJson = writeJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String name, Object value) throws IOException {
|
||||
|
||||
if (value instanceof String) {
|
||||
writeJson.writeStringField(name, (String)value);
|
||||
|
||||
} else {
|
||||
ScalarType scalarType = (ScalarType)typeManager.getScalarType(value.getClass());
|
||||
if (scalarType == null) {
|
||||
throw new IllegalArgumentException("unhandled type " + value.getClass());
|
||||
}
|
||||
scalarType.jsonWrite(writeJson, name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,27 +26,38 @@ public class WriteJson implements JsonWriter {
|
||||
|
||||
private final PathProperties pathProperties;
|
||||
|
||||
private final PathStack pathStack = new PathStack();
|
||||
private final PathStack pathStack;
|
||||
|
||||
private final ArrayStack<Object> parentBeans = new ArrayStack<Object>();
|
||||
private final ArrayStack<Object> parentBeans;
|
||||
|
||||
private final Object objectMapper;
|
||||
|
||||
private final JsonConfig.Include include;
|
||||
|
||||
/**
|
||||
* Construct for full bean use (normal).
|
||||
*/
|
||||
public WriteJson(SpiEbeanServer server, JsonGenerator generator, PathProperties pathProperties, Object objectMapper, JsonConfig.Include include){
|
||||
this.server = server;
|
||||
this.generator = generator;
|
||||
this.pathProperties = pathProperties;
|
||||
this.objectMapper = objectMapper;
|
||||
this.include = include;
|
||||
this.parentBeans = new ArrayStack<Object>();
|
||||
this.pathStack = new PathStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for testing purposes only.
|
||||
* Construct for Json scalar use.
|
||||
*/
|
||||
public WriteJson(JsonGenerator generator, JsonConfig.Include include) {
|
||||
this(null, generator, null, null, include);
|
||||
public WriteJson(JsonGenerator generator, JsonConfig.Include include){
|
||||
this.generator = generator;
|
||||
this.include = include;
|
||||
this.server = null;
|
||||
this.pathProperties = null;
|
||||
this.objectMapper = null;
|
||||
this.parentBeans = null;
|
||||
this.pathStack = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,9 @@ package com.avaje.ebeaninternal.server.transaction;
|
||||
import com.avaje.ebean.TransactionCallback;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.config.PersistBatch;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
|
||||
import com.avaje.ebean.event.changelog.BeanChange;
|
||||
import com.avaje.ebean.event.changelog.ChangeSet;
|
||||
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
@@ -10,7 +13,6 @@ import com.avaje.ebeaninternal.server.core.PersistRequest;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchControl;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -19,7 +21,12 @@ import javax.persistence.RollbackException;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JDBC Connection based transaction.
|
||||
@@ -133,6 +140,8 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
|
||||
protected boolean batchOnCascadeSet;
|
||||
|
||||
protected TChangeLogHolder changeLogHolder;
|
||||
|
||||
/**
|
||||
* Create a new JdbcTransaction.
|
||||
*/
|
||||
@@ -180,6 +189,19 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
return logPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBeanChange(BeanChange beanChange) {
|
||||
if (changeLogHolder == null) {
|
||||
changeLogHolder = new TChangeLogHolder(this, 100);
|
||||
}
|
||||
changeLogHolder.addBeanChange(beanChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChangeLog(ChangeSet changesRequest) {
|
||||
manager.sendChangeLog(changesRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TransactionCallback callback) {
|
||||
if (callbackList == null) {
|
||||
@@ -210,6 +232,9 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changeLogHolder != null) {
|
||||
changeLogHolder.postRollback();
|
||||
}
|
||||
}
|
||||
|
||||
protected void firePreCommit() {
|
||||
@@ -234,6 +259,9 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changeLogHolder != null) {
|
||||
changeLogHolder.postCommit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebean.event.changelog.BeanChange;
|
||||
import com.avaje.ebean.event.changelog.ChangeSet;
|
||||
import com.avaje.ebean.event.changelog.TxnState;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Holder of the changes handling the case when we send the changes
|
||||
* prior to commit or rollback as we hit the allowed 'batch size'.
|
||||
*/
|
||||
public class TChangeLogHolder {
|
||||
|
||||
/**
|
||||
* The owning transaction.
|
||||
*/
|
||||
private final SpiTransaction owner;
|
||||
|
||||
/**
|
||||
* A transaction id that can be used to join many changeSets when
|
||||
* we send multiple for a large transaction.
|
||||
*/
|
||||
private final String transactionId;
|
||||
|
||||
/**
|
||||
* When we hit batch size then send the changeSet even when the tranaction
|
||||
* has not yet completed.
|
||||
*/
|
||||
private final int batchSize;
|
||||
|
||||
/**
|
||||
* The changes we collect to send to the listener.
|
||||
*/
|
||||
private ChangeSet changes;
|
||||
|
||||
private long batchId;
|
||||
|
||||
/**
|
||||
* Counter to check when we hit the batch size.
|
||||
*/
|
||||
private int count;
|
||||
|
||||
/**
|
||||
* Construct with the owning transaction and batch size to use.
|
||||
*/
|
||||
public TChangeLogHolder(SpiTransaction owner, int batchSize) {
|
||||
this.owner = owner;
|
||||
this.transactionId = UUID.randomUUID().toString();
|
||||
this.batchSize = batchSize;
|
||||
this.changes = new ChangeSet(transactionId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bean change to the change set.
|
||||
*/
|
||||
public void addBeanChange(BeanChange change) {
|
||||
|
||||
changes.addBeanChange(change);
|
||||
if (++count >= batchSize) {
|
||||
// we hit the batch size so send what we have knowing
|
||||
// that the transaction has not completed yet and
|
||||
// reset the changes and count
|
||||
owner.sendChangeLog(changes);
|
||||
changes = new ChangeSet(transactionId, ++batchId);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On post commit send the changes we have collected.
|
||||
*/
|
||||
public void postCommit() {
|
||||
changes.setTxnState(TxnState.COMMITTED);
|
||||
owner.sendChangeLog(changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* On post rollback send the changes we have collected and
|
||||
* leave it up to the listener to decide what to do.
|
||||
*/
|
||||
public void postRollback() {
|
||||
changes.setTxnState(TxnState.ROLLBACK);
|
||||
owner.sendChangeLog(changes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
|
||||
import com.avaje.ebean.dbmigration.DbOffline;
|
||||
import com.avaje.ebean.event.TransactionEventListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
|
||||
import com.avaje.ebean.event.changelog.ChangeSet;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable;
|
||||
@@ -80,6 +83,17 @@ public class TransactionManager {
|
||||
|
||||
protected final TransactionEventListener[] transactionEventListeners;
|
||||
|
||||
/**
|
||||
* Used to prepare the change set setting user context information in the
|
||||
* foreground thread before logging.
|
||||
*/
|
||||
private final ChangeLogPrepare changeLogPrepare;
|
||||
|
||||
/**
|
||||
* Performs the actual logging of the change set in background.
|
||||
*/
|
||||
private final ChangeLogListener changeLogListener;
|
||||
|
||||
/**
|
||||
* Create the TransactionManager
|
||||
*/
|
||||
@@ -89,6 +103,8 @@ public class TransactionManager {
|
||||
this.persistBatch = config.getPersistBatch();
|
||||
this.persistBatchOnCascade = config.appliedPersistBatchOnCascade();
|
||||
this.beanDescriptorManager = descMgr;
|
||||
this.changeLogPrepare = descMgr.getChangeLogPrepare();
|
||||
this.changeLogListener = descMgr.getChangeLogListener();
|
||||
this.clusterManager = clusterManager;
|
||||
this.serverName = config.getName();
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
@@ -439,4 +455,21 @@ public class TransactionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and then send/log the changeSet.
|
||||
*/
|
||||
public void sendChangeLog(final ChangeSet changeSet) {
|
||||
|
||||
// can set userId, userIpAddress & userContext if desired
|
||||
if (changeLogPrepare.prepare(changeSet)) {
|
||||
|
||||
// call the log method in background
|
||||
backgroundExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
changeLogListener.log(changeSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user