package org.infinispan.objectfilter.impl.predicateindex; import java.io.IOException; import org.infinispan.protostream.MessageContext; import org.infinispan.protostream.ProtobufParser; import org.infinispan.protostream.SerializationContext; import org.infinispan.protostream.TagHandler; import org.infinispan.protostream.WrappedMessage; import org.infinispan.protostream.descriptors.Descriptor; import org.infinispan.protostream.descriptors.FieldDescriptor; import org.infinispan.protostream.descriptors.JavaType; /** * @author anistor@redhat.com * @since 7.0 */ public final class ProtobufMatcherEvalContext extends MatcherEvalContext<Descriptor, FieldDescriptor, Integer> implements TagHandler { private boolean payloadStarted = false; private int skipping = 0; private byte[] payload; private String entityTypeName; private Descriptor payloadMessageDescriptor; private MessageContext messageContext; private final SerializationContext serializationContext; public ProtobufMatcherEvalContext(Object userContext, Object eventType, Object instance, Descriptor wrappedMessageDescriptor, SerializationContext serializationContext) { super(userContext, eventType, instance); this.serializationContext = serializationContext; try { ProtobufParser.INSTANCE.parse(this, wrappedMessageDescriptor, (byte[]) getInstance()); } catch (IOException e) { throw new RuntimeException(e); // TODO [anistor] proper exception handling needed } } @Override public Descriptor getEntityType() { return payloadMessageDescriptor; } @Override public void onStart() { } //todo [anistor] missing tags need to be fired with default value defined in proto schema or null if they admit null; missing messages need to be fired with null at end of the nesting level. BTW, seems like this is better to be included in Protostream as a feature @Override public void onTag(int fieldNumber, FieldDescriptor fieldDescriptor, Object tagValue) { if (payloadStarted) { if (skipping == 0) { AttributeNode<FieldDescriptor, Integer> attrNode = currentNode.getChild(fieldNumber); if (attrNode != null) { // process only 'interesting' tags messageContext.markField(fieldNumber); attrNode.processValue(tagValue, this); } } } else { switch (fieldNumber) { case WrappedMessage.WRAPPED_DESCRIPTOR_FULL_NAME: entityTypeName = (String) tagValue; break; case WrappedMessage.WRAPPED_DESCRIPTOR_ID: entityTypeName = serializationContext.getTypeNameById((Integer) tagValue); break; case WrappedMessage.WRAPPED_MESSAGE: payload = (byte[]) tagValue; break; case WrappedMessage.WRAPPED_DOUBLE: case WrappedMessage.WRAPPED_FLOAT: case WrappedMessage.WRAPPED_INT64: case WrappedMessage.WRAPPED_UINT64: case WrappedMessage.WRAPPED_INT32: case WrappedMessage.WRAPPED_FIXED64: case WrappedMessage.WRAPPED_FIXED32: case WrappedMessage.WRAPPED_BOOL: case WrappedMessage.WRAPPED_STRING: case WrappedMessage.WRAPPED_BYTES: case WrappedMessage.WRAPPED_UINT32: case WrappedMessage.WRAPPED_SFIXED32: case WrappedMessage.WRAPPED_SFIXED64: case WrappedMessage.WRAPPED_SINT32: case WrappedMessage.WRAPPED_SINT64: case WrappedMessage.WRAPPED_ENUM: break; // this is a primitive value, which we ignore for now due to lack of support for querying primitives default: throw new IllegalStateException("Unexpected field : " + fieldNumber); } } } @Override public void onStartNested(int fieldNumber, FieldDescriptor fieldDescriptor) { if (payloadStarted) { if (skipping == 0) { AttributeNode<FieldDescriptor, Integer> attrNode = currentNode.getChild(fieldNumber); if (attrNode != null) { // ignore 'uninteresting' tags messageContext.markField(fieldNumber); pushContext(fieldDescriptor.getName(), fieldDescriptor.getMessageType()); currentNode = attrNode; return; } } // found an uninteresting nesting level, start skipping from here on until this level ends skipping++; } else { throw new IllegalStateException("No nested message is supported"); } } @Override public void onEndNested(int fieldNumber, FieldDescriptor fieldDescriptor) { if (payloadStarted) { if (skipping == 0) { popContext(); currentNode = currentNode.getParent(); } else { skipping--; } } else { throw new IllegalStateException("No nested message is supported"); } } @Override public void onEnd() { if (payloadStarted) { processMissingFields(); } else { payloadStarted = true; if (payload != null) { if (entityTypeName == null) { throw new IllegalStateException("Descriptor name is missing"); } payloadMessageDescriptor = serializationContext.getMessageDescriptor(entityTypeName); messageContext = new MessageContext<>(null, null, payloadMessageDescriptor); } } } @Override protected void processAttributes(AttributeNode<FieldDescriptor, Integer> node, Object instance) { try { ProtobufParser.INSTANCE.parse(this, payloadMessageDescriptor, payload); } catch (IOException e) { throw new RuntimeException(e); // TODO [anistor] proper exception handling needed } } private void pushContext(String fieldName, Descriptor messageDescriptor) { messageContext = new MessageContext<>(messageContext, fieldName, messageDescriptor); } private void popContext() { processMissingFields(); messageContext = messageContext.getParentContext(); } private void processMissingFields() { for (FieldDescriptor fd : messageContext.getMessageDescriptor().getFields()) { AttributeNode<FieldDescriptor, Integer> attributeNode = currentNode.getChild(fd.getNumber()); boolean fieldSeen = messageContext.isFieldMarked(fd.getNumber()); if (attributeNode != null && (fd.isRepeated() || !fieldSeen)) { if (fd.isRepeated()) { // Repeated fields can't have default values but we need to at least take care of IS [NOT] NULL predicates if (fieldSeen) { // Here we use a dummy value since it would not matter anyway for IS [NOT] NULL attributeNode.processValue(AttributeNode.DUMMY_VALUE, this); } else { processNullAttribute(attributeNode); } } else { if (fd.getJavaType() == JavaType.MESSAGE) { processNullAttribute(attributeNode); } else { Object defaultValue = fd.hasDefaultValue() ? fd.getDefaultValue() : null; attributeNode.processValue(defaultValue, this); } } } } } private void processNullAttribute(AttributeNode<FieldDescriptor, Integer> attributeNode) { attributeNode.processValue(null, this); for (AttributeNode<FieldDescriptor, Integer> childAttribute : attributeNode.getChildren()) { processNullAttribute(childAttribute); } } }