package org.rakam.analysis.metadata; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import org.rakam.collection.FieldDependencyBuilder; import org.rakam.collection.SchemaField; import org.rakam.util.NotExistsException; import org.rakam.util.RakamException; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static java.lang.String.format; public class SchemaChecker { private final Metastore metastore; private final FieldDependencyBuilder.FieldDependency fieldDependency; @Inject public SchemaChecker(Metastore metastore, FieldDependencyBuilder.FieldDependency fieldDependency) { this.metastore = metastore; this.fieldDependency = fieldDependency; } public HashSet<SchemaField> checkNewFields(String collection, Set<SchemaField> newFields) { HashSet<SchemaField> fields = new HashSet<>(newFields); Iterator<SchemaField> it = fields.iterator(); while (it.hasNext()) { SchemaField newField = it.next(); if (fieldDependency.dependentFields.containsKey(newField)) { it.remove(); } if (newField.getName().startsWith("_")) { for (Map.Entry<String, List<SchemaField>> entry : fieldDependency.dependentFields.entrySet()) { Optional<SchemaField> collision = entry.getValue().stream() .filter(e -> e.getName().equals(newField.getName()) && !e.getType().equals(e.getType())) .findAny(); if (collision.isPresent()) { throw new RakamException(format("Field %s.%s collides with one of the magic field with type %s", collection, newField.getName(), collision.get().getType()), BAD_REQUEST); } } } } fieldDependency.constantFields.forEach(field -> addModuleField(fields, field)); fieldDependency.dependentFields.forEach((fieldName, field) -> addConditionalModuleField(fields, fieldName, field)); return fields; } private void addConditionalModuleField(Set<SchemaField> fields, String field, List<SchemaField> newFields) { if (fields.stream().anyMatch(f -> f.getName().equals(field))) { newFields.forEach(newField -> addModuleField(fields, newField)); } } private void addModuleField(Set<SchemaField> fields, SchemaField newField) { Iterator<SchemaField> iterator = fields.iterator(); while (iterator.hasNext()) { SchemaField field = iterator.next(); if (field.getName().equals(newField.getName())) { if (field.getType().equals(newField.getType())) { return; } else { iterator.remove(); break; } } } fields.add(newField); } // @PostConstruct protected void checkExistingSchema() { for (String project : metastore.getProjects()) { Map<String, List<SchemaField>> collections = metastore.getCollections(project); collections.forEach((collection, fields) -> { Set<SchemaField> collect = fieldDependency.constantFields.stream() .filter(constant -> !fields.stream() .anyMatch(existing -> check(project, collection, constant, existing))) .collect(Collectors.toSet()); fieldDependency.dependentFields.entrySet().stream().map(Map.Entry::getValue) .forEach(values -> values.stream().forEach(value -> fields.stream().forEach(field -> check(project, collection, field, value)))); fields.forEach(field -> fieldDependency.dependentFields.getOrDefault(field.getName(), ImmutableList.of()).stream() .filter(dependentField -> !fields.stream() .anyMatch(existing -> check(project, collection, existing, dependentField))) .forEach(collect::add)); if (!collect.isEmpty()) { try { metastore.getOrCreateCollectionFieldList(project, collection, collect); } catch (NotExistsException e) { throw Throwables.propagate(e); } } }); } } private boolean check(String project, String collection, SchemaField existing, SchemaField moduleField) { if (existing.getName().equals(moduleField.getName())) { if (!existing.getType().equals(moduleField.getType())) { throw new IllegalStateException(format("Module field '%s' type does not match existing field in event of project %s.%s. Existing type: %s, Module field type: %s. \n" + "Please change the schema manually of disable the module.", existing.getName(), project, collection, existing.getType(), moduleField.getType())); } return true; } return false; } }