package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.id.IdBinder;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Wraps the information representing a Inserted Updated or Deleted Bean.
* <p>
* This information is broadcast across the cluster so that remote BeanListeners
* are notified of the inserts updates and deletes that occurred.
* </p>
* <p>
* You control it the data is broadcast and what data is broadcast by the
* BeanListener.getClusterData() method. It is guessed that often just the Id
* property or perhaps a few properties in a Map will be broadcast to reduce the
* size of data sent around the network.
* </p>
*/
public class BeanPersistIds {
private final BeanDescriptor<?> beanDescriptor;
private final String descriptorId;
private List<Object> insertIds;
private List<Object> updateIds;
private List<Object> deleteIds;
/**
* Create the payload.
*/
public BeanPersistIds(BeanDescriptor<?> desc) {
this.beanDescriptor = desc;
this.descriptorId = desc.getDescriptorId();
}
public static BeanPersistIds readBinaryMessage(SpiEbeanServer server, DataInput dataInput) throws IOException {
String descriptorId = dataInput.readUTF();
BeanDescriptor<?> desc = server.getBeanDescriptorById(descriptorId);
BeanPersistIds bp = new BeanPersistIds(desc);
bp.read(dataInput);
return bp;
}
private void read(DataInput dataInput) throws IOException {
IdBinder idBinder = beanDescriptor.getIdBinder();
int iudType = dataInput.readInt();
List<Object> idList = readIdList(dataInput, idBinder);
switch (iudType) {
case 0:
insertIds = idList;
break;
case 1:
updateIds = idList;
break;
case 2:
deleteIds = idList;
break;
default:
throw new RuntimeException("Invalid iudType " + iudType);
}
}
/**
* Write the contents into a BinaryMessage form.
* <p>
* For a RemoteBeanPersist with a large number of id's note that this is
* broken up into many BinaryMessages each with a maximum of 100 ids. This
* enables the contents of a large RemoteTransactionEvent to be split up
* across multiple Packets.
* </p>
*/
void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
writeIdList(beanDescriptor, 0, insertIds, msgList);
writeIdList(beanDescriptor, 1, updateIds, msgList);
writeIdList(beanDescriptor, 2, deleteIds, msgList);
}
private List<Object> readIdList(DataInput dataInput, IdBinder idBinder) throws IOException {
int count = dataInput.readInt();
if (count < 1) {
return null;
}
List<Object> idList = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
idList.add(idBinder.readData(dataInput));
}
return idList;
}
/**
* Write a BinaryMessage containing the descriptorId, iudType and list of Id
* values.
* <p>
* Note that a given BinaryMessage has a maximum of 100 Ids. This is due to
* the limit of UDP packet sizes. We break up the RemoteBeanPersist into
* potentially many smaller BinaryMessages which may be put into multiple
* Packets.
* </p>
*/
private void writeIdList(BeanDescriptor<?> desc, int iudType, List<Object> idList, BinaryMessageList msgList) throws IOException {
IdBinder idBinder = desc.getIdBinder();
int count = idList == null ? 0 : idList.size();
if (count > 0) {
int loop = 0;
int i = 0;
int eof = idList.size();
do {
++loop;
int endOfLoop = Math.min(eof, loop * 100);
BinaryMessage m = new BinaryMessage(endOfLoop * 4 + 20);
DataOutputStream os = m.getOs();
os.writeInt(BinaryMessage.TYPE_BEANIUD);
os.writeUTF(descriptorId);
os.writeInt(iudType);
os.writeInt(count);
for (; i < endOfLoop; i++) {
idBinder.writeData(os, idList.get(i));
}
os.flush();
msgList.add(m);
} while (i < eof);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (beanDescriptor != null) {
sb.append(beanDescriptor.getFullName());
} else {
sb.append("descId:").append(descriptorId);
}
if (insertIds != null) {
sb.append(" insertIds:").append(insertIds);
}
if (updateIds != null) {
sb.append(" updateIds:").append(updateIds);
}
if (deleteIds != null) {
sb.append(" deleteIds:").append(deleteIds);
}
return sb.toString();
}
void addId(PersistRequest.Type type, Serializable id) {
switch (type) {
case INSERT:
addInsertId(id);
break;
case UPDATE:
addUpdateId(id);
break;
case DELETE:
case SOFT_DELETE:
addDeleteId(id);
break;
default:
break;
}
}
private void addInsertId(Serializable id) {
if (insertIds == null) {
insertIds = new ArrayList<>();
}
insertIds.add(id);
}
private void addUpdateId(Serializable id) {
if (updateIds == null) {
updateIds = new ArrayList<>();
}
updateIds.add(id);
}
private void addDeleteId(Serializable id) {
if (deleteIds == null) {
deleteIds = new ArrayList<>();
}
deleteIds.add(id);
}
public BeanDescriptor<?> getBeanDescriptor() {
return beanDescriptor;
}
List<Object> getDeleteIds() {
return deleteIds;
}
/**
* Notify the cache of this event that came from another server in the cluster.
*/
void notifyCacheAndListener() {
// any change invalidates the query cache
beanDescriptor.clearQueryCache();
if (updateIds != null) {
for (Object id : updateIds) {
beanDescriptor.cacheHandleDeleteById(id);
}
}
if (deleteIds != null) {
for (Object id : deleteIds) {
beanDescriptor.cacheHandleDeleteById(id);
}
}
}
}