/** * Populates a map of protobuf extensions and map with the default values for each message field * from a map of file descriptors. * * @param fileDescriptorMap Map of file descriptors * @param typeToExtensionMap Map of extensions to populate * @param defaultValueMap Map of default values to populate */ public static void populateDefaultsAndExtensions( Map<String, Descriptors.FileDescriptor> fileDescriptorMap, Map<String, Set<Descriptors.FieldDescriptor>> typeToExtensionMap, Map<String, Object> defaultValueMap) { for (Descriptors.FileDescriptor f : fileDescriptorMap.values()) { // go over every file descriptor and look for extensions and default values of those // extensions for (Descriptors.FieldDescriptor fieldDescriptor : f.getExtensions()) { String containingType = fieldDescriptor.getContainingType().getFullName(); Set<Descriptors.FieldDescriptor> fieldDescriptors = typeToExtensionMap.get(containingType); if (fieldDescriptors == null) { fieldDescriptors = new LinkedHashSet<>(); typeToExtensionMap.put(containingType, fieldDescriptors); } fieldDescriptors.add(fieldDescriptor); if (fieldDescriptor.hasDefaultValue()) { defaultValueMap.put( containingType + "." + fieldDescriptor.getName(), fieldDescriptor.getDefaultValue()); } } // go over messages within file descriptor and look for all fields and extensions and their // defaults for (Descriptors.Descriptor d : f.getMessageTypes()) { addDefaultsAndExtensions(typeToExtensionMap, defaultValueMap, d); } } }
private static void handleNonRepeatedField( Record record, Map<String, Field> valueAsMap, String fieldPath, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Map<String, Object> defaultValueMap, Descriptors.Descriptor desc, Descriptors.FieldDescriptor f, DynamicMessage.Builder builder) throws DataGeneratorException { Object val; String keyName = f.getName(); if (valueAsMap.containsKey(keyName)) { val = getValue( f, valueAsMap.get(keyName), record, fieldPath + FORWARD_SLASH + f.getName(), messageTypeToExtensionMap, defaultValueMap); } else { // record does not contain field, look up default value String key = desc.getFullName() + "." + f.getName(); if (!defaultValueMap.containsKey(key) && !f.isOptional()) { throw new DataGeneratorException(Errors.PROTOBUF_04, record.getHeader().getSourceId(), key); } val = defaultValueMap.get(key); } if (val != null) { builder.setField(f, val); } }
private static void addDefaultsAndExtensions( Map<String, Set<Descriptors.FieldDescriptor>> e, Map<String, Object> defaultValueMap, Descriptors.Descriptor d) { for (Descriptors.FieldDescriptor fieldDescriptor : d.getExtensions()) { String containingType = fieldDescriptor.getContainingType().getFullName(); Set<Descriptors.FieldDescriptor> fieldDescriptors = e.get(containingType); if (fieldDescriptors == null) { fieldDescriptors = new LinkedHashSet<>(); e.put(containingType, fieldDescriptors); } fieldDescriptors.add(fieldDescriptor); if (fieldDescriptor.hasDefaultValue()) { defaultValueMap.put( fieldDescriptor.getContainingType().getFullName() + "." + fieldDescriptor.getName(), fieldDescriptor.getDefaultValue()); } } for (Descriptors.FieldDescriptor fieldDescriptor : d.getFields()) { if (fieldDescriptor.hasDefaultValue()) { defaultValueMap.put( d.getFullName() + "." + fieldDescriptor.getName(), fieldDescriptor.getDefaultValue()); } } for (Descriptors.Descriptor nestedType : d.getNestedTypes()) { addDefaultsAndExtensions(e, defaultValueMap, nestedType); } }
private static Object getValue( Descriptors.FieldDescriptor f, Field field, Record record, String protoFieldPath, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Map<String, Object> defaultValueMap) throws DataGeneratorException { Object value = null; try { if (field.getValue() != null) { switch (f.getJavaType()) { case BOOLEAN: value = field.getValueAsBoolean(); break; case BYTE_STRING: value = ByteString.copyFrom(field.getValueAsByteArray()); break; case DOUBLE: value = field.getValueAsDouble(); break; case ENUM: value = f.getEnumType().findValueByName(field.getValueAsString()); break; case FLOAT: value = field.getValueAsFloat(); break; case INT: value = field.getValueAsInteger(); break; case LONG: value = field.getValueAsLong(); break; case STRING: value = field.getValueAsString(); break; case MESSAGE: Descriptors.Descriptor messageType = f.getMessageType(); value = sdcFieldToProtobufMsg( record, field, protoFieldPath, messageType, messageTypeToExtensionMap, defaultValueMap); break; default: throw new DataGeneratorException(Errors.PROTOBUF_03, f.getJavaType().name()); } } } catch (IllegalArgumentException e) { throw new DataGeneratorException( Errors.PROTOBUF_11, field.getValue(), f.getJavaType().name(), e); } return value; }
/** * Serializes a field path in a record to a protobuf message using the specified descriptor. * * @param record Record with the field to serialize * @param field The field to serialize * @param fieldPath The field path of the specified field * @param desc Protobuf descriptor * @param messageTypeToExtensionMap Protobuf extension map * @param defaultValueMap Protobuf default field values * @return serialized message * @throws DataGeneratorException */ private static DynamicMessage sdcFieldToProtobufMsg( Record record, Field field, String fieldPath, Descriptors.Descriptor desc, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Map<String, Object> defaultValueMap) throws DataGeneratorException { if (field == null) { return null; } // compute all fields to look for including extensions DynamicMessage.Builder builder = DynamicMessage.newBuilder(desc); List<Descriptors.FieldDescriptor> fields = new ArrayList<>(); fields.addAll(desc.getFields()); if (messageTypeToExtensionMap.containsKey(desc.getFullName())) { fields.addAll(messageTypeToExtensionMap.get(desc.getFullName())); } // root field is always a Map in a record representing protobuf data Map<String, Field> valueAsMap = field.getValueAsMap(); for (Descriptors.FieldDescriptor f : fields) { Field mapField = valueAsMap.get(f.getName()); // Repeated field if (f.isMapField()) { handleMapField( record, mapField, fieldPath, messageTypeToExtensionMap, defaultValueMap, f, builder); } else if (f.isRepeated()) { handleRepeatedField( record, mapField, fieldPath, messageTypeToExtensionMap, defaultValueMap, f, builder); } else { // non repeated field handleNonRepeatedField( record, valueAsMap, fieldPath, messageTypeToExtensionMap, defaultValueMap, desc, f, builder); } } // if record has unknown fields for this field path, handle it try { handleUnknownFields(record, fieldPath, builder); } catch (IOException e) { throw new DataGeneratorException(Errors.PROTOBUF_05, e.toString(), e); } return builder.build(); }
private static void handleMapField( Record record, Field field, String fieldPath, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Map<String, Object> defaultValueMap, Descriptors.FieldDescriptor fieldDescriptor, DynamicMessage.Builder builder) throws DataGeneratorException { Descriptors.Descriptor mapEntryDescriptor = fieldDescriptor.getMessageType(); // MapEntry contains key and value fields Map<String, Field> sdcMapField = field.getValueAsMap(); for (Map.Entry<String, Field> entry : sdcMapField.entrySet()) { builder.addRepeatedField( fieldDescriptor, DynamicMessage.newBuilder(mapEntryDescriptor) .setField(mapEntryDescriptor.findFieldByName(KEY), entry.getKey()) .setField( mapEntryDescriptor.findFieldByName(VALUE), getValue( mapEntryDescriptor.findFieldByName(VALUE), entry.getValue(), record, fieldPath + FORWARD_SLASH + entry.getKey(), messageTypeToExtensionMap, defaultValueMap)) .build()); } }
private static Field createSdcField( Record record, String fieldPath, Descriptors.FieldDescriptor fieldDescriptor, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Object value) throws DataParserException { Field f; switch (fieldDescriptor.getJavaType()) { case BOOLEAN: f = Field.create(Field.Type.BOOLEAN, value); break; case BYTE_STRING: f = Field.create(Field.Type.BYTE_ARRAY, ((ByteString) value).toByteArray()); break; case DOUBLE: f = Field.create(Field.Type.DOUBLE, value); break; case ENUM: f = Field.create(Field.Type.STRING, ((Descriptors.EnumValueDescriptor) value).getName()); break; case FLOAT: f = Field.create(Field.Type.FLOAT, value); break; case INT: f = Field.create(Field.Type.INTEGER, value); break; case LONG: f = Field.create(Field.Type.LONG, value); break; case STRING: f = Field.create(Field.Type.STRING, value); break; case MESSAGE: f = protobufToSdcField( record, fieldPath + FORWARD_SLASH + fieldDescriptor.getName(), fieldDescriptor.getMessageType(), messageTypeToExtensionMap, value); break; default: throw new DataParserException(Errors.PROTOBUF_03, fieldDescriptor.getJavaType().name()); } return f; }
private static void handleRepeatedField( Record record, Field field, String fieldPath, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Map<String, Object> defaultValueMap, Descriptors.FieldDescriptor f, DynamicMessage.Builder builder) throws DataGeneratorException { List<Object> toReturn = new ArrayList<>(); List<Field> valueAsList = field.getValueAsList(); if (valueAsList != null) { // According to proto 2 and 3 language guide repeated fields can have 0 elements. // Also null is treated as empty in case of json mappings so I guess we can ignore if it is // null. for (int i = 0; i < valueAsList.size(); i++) { if (f.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { // repeated field of type message toReturn.add( sdcFieldToProtobufMsg( record, valueAsList.get(i), fieldPath + FORWARD_SLASH + f.getName() + "[" + i + "]", f.getMessageType(), messageTypeToExtensionMap, defaultValueMap)); } else { // repeated field of primitive types toReturn.add( getValue( f, valueAsList.get(i), record, fieldPath + FORWARD_SLASH + f.getName(), messageTypeToExtensionMap, defaultValueMap)); } } } builder.setField(f, toReturn); }
private static void handleRepeatedField( Record record, Field field, String fieldPath, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Map<String, Object> defaultValueMap, Descriptors.FieldDescriptor f, DynamicMessage.Builder builder) throws DataGeneratorException { List<Object> toReturn = new ArrayList<>(); List<Field> valueAsList = field.getValueAsList(); for (int i = 0; i < valueAsList.size(); i++) { if (f.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { // repeated field of type message toReturn.add( sdcFieldToProtobufMsg( record, valueAsList.get(i), fieldPath + FORWARD_SLASH + f.getName() + "[" + i + "]", f.getMessageType(), messageTypeToExtensionMap, defaultValueMap)); } else { // repeated field of primitive types toReturn.add( getValue( f, valueAsList.get(i), record, fieldPath + FORWARD_SLASH + f.getName(), messageTypeToExtensionMap, defaultValueMap)); } } builder.setField(f, toReturn); }
/** * Creates an SDC Record Field from the provided protobuf message and descriptor. * * @param record record to be augmented * @param fieldPath field path in the record to insert new Field * @param fieldDescriptor descriptor for this message * @param messageTypeToExtensionMap protobuf type extensions * @param message protobuf message to decode * @return reference to the Field added to the record. * @throws DataParserException */ @SuppressWarnings("unchecked") private static Field createField( Record record, String fieldPath, Descriptors.FieldDescriptor fieldDescriptor, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Object message) throws DataParserException { Field newField; if (message == null) { // If the message does not contain required fields then builder.build() throws // UninitializedMessageException Object defaultValue = null; // get default values only for optional fields and non-message types if (fieldDescriptor.isOptional() && fieldDescriptor.getJavaType() != Descriptors.FieldDescriptor.JavaType.MESSAGE) { defaultValue = fieldDescriptor.getDefaultValue(); } newField = Field.create(getFieldType(fieldDescriptor.getJavaType()), defaultValue); } else if (fieldDescriptor.isMapField()) { // Map entry (protobuf 3 map) Map<String, Field> sdcMapFieldValues = new HashMap<>(); Collection<DynamicMessage> mapEntries = (Collection<DynamicMessage>) message; // MapEntry for (DynamicMessage dynamicMessage : mapEntries) { // MapEntry has 2 fields, key and value Map<Descriptors.FieldDescriptor, Object> kv = dynamicMessage.getAllFields(); String key = null; Object value = null; Descriptors.FieldDescriptor valueDescriptor = null; for (Map.Entry<Descriptors.FieldDescriptor, Object> entry : kv.entrySet()) { switch (entry.getKey().getName()) { case KEY: key = entry.getValue().toString(); break; case VALUE: value = entry.getValue(); valueDescriptor = entry.getKey(); break; default: throw new DataParserException(Errors.PROTOBUF_09, entry.getKey().getName()); } } if (key != null && valueDescriptor != null) { sdcMapFieldValues.put( key, createSdcField(record, fieldPath, valueDescriptor, messageTypeToExtensionMap, value)); } } newField = Field.create(sdcMapFieldValues); } else if (fieldDescriptor.isRepeated()) { // List entry (repeated) List<?> list = (List<?>) message; List<Field> listField = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { if (fieldDescriptor.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { listField.add( protobufToSdcField( record, fieldPath + "[" + i + "]", fieldDescriptor.getMessageType(), messageTypeToExtensionMap, list.get(i))); } else { listField.add( createSdcField( record, fieldPath + "[" + i + "]", fieldDescriptor, messageTypeToExtensionMap, list.get(i))); } } newField = Field.create(listField); } else { // normal entry newField = createSdcField(record, fieldPath, fieldDescriptor, messageTypeToExtensionMap, message); } return newField; }
/** * Converts a protobuf message to an SDC Record Field. * * @param record SDC Record to add field to * @param fieldPath location in record where to insert field. * @param descriptor protobuf descriptor instance * @param messageTypeToExtensionMap protobuf extensions map * @param message message to decode and insert into the specified field path * @return new Field instance representing the decoded message * @throws DataParserException */ public static Field protobufToSdcField( Record record, String fieldPath, Descriptors.Descriptor descriptor, Map<String, Set<Descriptors.FieldDescriptor>> messageTypeToExtensionMap, Object message) throws DataParserException { Map<String, Field> sdcRecordMapFieldValue = new HashMap<>(); // get all the expected fields from the proto file Map<String, Descriptors.FieldDescriptor> protobufFields = new LinkedHashMap<>(); for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) { protobufFields.put(fieldDescriptor.getName(), fieldDescriptor); } // get all fields in the read message Map<Descriptors.FieldDescriptor, Object> values = ((DynamicMessage) message).getAllFields(); // for every field present in the proto definition create an sdc field. for (Descriptors.FieldDescriptor fieldDescriptor : protobufFields.values()) { Object value = values.get(fieldDescriptor); sdcRecordMapFieldValue.put( fieldDescriptor.getName(), createField(record, fieldPath, fieldDescriptor, messageTypeToExtensionMap, value)); } // handle applicable extensions for this message type if (messageTypeToExtensionMap.containsKey(descriptor.getFullName())) { for (Descriptors.FieldDescriptor fieldDescriptor : messageTypeToExtensionMap.get(descriptor.getFullName())) { if (values.containsKey(fieldDescriptor)) { Object value = values.get(fieldDescriptor); sdcRecordMapFieldValue.put( fieldDescriptor.getName(), createField(record, fieldPath, fieldDescriptor, messageTypeToExtensionMap, value)); } } } // handle unknown fields // unknown fields can go into the record header UnknownFieldSet unknownFields = ((DynamicMessage) message).getUnknownFields(); if (!unknownFields.asMap().isEmpty()) { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); try { unknownFields.writeDelimitedTo(bOut); bOut.flush(); bOut.close(); } catch (IOException e) { throw new DataParserException(Errors.PROTOBUF_10, e.toString(), e); } String path = fieldPath.isEmpty() ? FORWARD_SLASH : fieldPath; byte[] bytes = org.apache.commons.codec.binary.Base64.encodeBase64(bOut.toByteArray()); record .getHeader() .setAttribute( PROTOBUF_UNKNOWN_FIELDS_PREFIX + path, new String(bytes, StandardCharsets.UTF_8)); } return Field.create(sdcRecordMapFieldValue); }
/** * Gets requirements from all configs. Merges whitelists of requirements with 'extends' equal to * 'rule_id' of other rule. */ static List<Requirement> mergeRequirements( AbstractCompiler compiler, List<ConformanceConfig> configs) { List<Requirement.Builder> builders = new ArrayList<>(); Map<String, Requirement.Builder> extendable = new HashMap<>(); for (ConformanceConfig config : configs) { for (Requirement requirement : config.getRequirementList()) { Requirement.Builder builder = requirement.toBuilder(); if (requirement.hasRuleId()) { if (requirement.getRuleId().isEmpty()) { reportInvalidRequirement(compiler, requirement, "empty rule_id"); continue; } if (extendable.containsKey(requirement.getRuleId())) { reportInvalidRequirement( compiler, requirement, "two requirements with the same rule_id: " + requirement.getRuleId()); continue; } extendable.put(requirement.getRuleId(), builder); } if (!requirement.hasExtends()) { builders.add(builder); } } } for (ConformanceConfig config : configs) { for (Requirement requirement : config.getRequirementList()) { if (requirement.hasExtends()) { Requirement.Builder existing = extendable.get(requirement.getExtends()); if (existing == null) { reportInvalidRequirement( compiler, requirement, "no requirement with rule_id: " + requirement.getExtends()); continue; } for (Descriptors.FieldDescriptor field : requirement.getAllFields().keySet()) { if (!EXTENDABLE_FIELDS.contains(field.getName())) { reportInvalidRequirement( compiler, requirement, "extending rules allow only " + EXTENDABLE_FIELDS); } } existing.addAllWhitelist(requirement.getWhitelistList()); existing.addAllWhitelistRegexp(requirement.getWhitelistRegexpList()); existing.addAllOnlyApplyTo(requirement.getOnlyApplyToList()); existing.addAllOnlyApplyToRegexp(requirement.getOnlyApplyToRegexpList()); } } } List<Requirement> requirements = new ArrayList<>(builders.size()); for (Requirement.Builder builder : builders) { Requirement requirement = builder.build(); checkRequirementList(compiler, requirement, "whitelist"); checkRequirementList(compiler, requirement, "whitelist_regexp"); checkRequirementList(compiler, requirement, "only_apply_to"); checkRequirementList(compiler, requirement, "only_apply_to_regexp"); requirements.add(requirement); } return requirements; }