All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.carml.engine.RmlMapper Maven / Gradle / Ivy

package io.carml.engine;

import static io.carml.util.LogUtil.exception;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toSet;

import com.google.common.collect.Iterables;
import io.carml.logicalsourceresolver.LogicalSourceRecord;
import io.carml.logicalsourceresolver.LogicalSourceResolver;
import io.carml.logicalsourceresolver.ResolvedSource;
import io.carml.model.LogicalSource;
import io.carml.model.NameableStream;
import io.carml.model.TriplesMap;
import io.carml.util.Mappings;
import java.io.InputStream;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import reactor.core.publisher.Flux;

@Slf4j
@AllArgsConstructor
public abstract class RmlMapper {
  public static final String DEFAULT_STREAM_NAME = "DEFAULT";

  @Getter
  private Set triplesMaps;

  private Function> sourceResolver;

  private Set> triplesMappers;

  private Map, TriplesMapper> refObjectMapperToParentTriplesMapper;

  private Map> sourceToLogicalSourceResolver;

  public  Flux mapRecord(R providedRecord, Class providedRecordClass) {
    return mapRecord(providedRecord, providedRecordClass, Set.of());
  }

  public  Flux mapRecord(R providedRecord, Class providedRecordClass, Set triplesMapFilter) {
    return map(null, providedRecord, providedRecordClass, triplesMapFilter);
  }

  public Flux map() {
    return map(Map.of());
  }

  public Flux map(Set triplesMapFilter) {
    return map(Map.of(), triplesMapFilter);
  }

  public Flux map(@NonNull InputStream inputStream) {
    return map(Map.of(DEFAULT_STREAM_NAME, inputStream));
  }

  public Flux map(@NonNull InputStream inputStream, Set triplesMapFilter) {
    return map(Map.of(DEFAULT_STREAM_NAME, inputStream), triplesMapFilter);
  }

  public Flux map(Map namedInputStreams) {
    return map(namedInputStreams, Set.of());
  }

  public Flux map(Map namedInputStreams, Set triplesMapFilter) {
    return map(namedInputStreams, null, null, triplesMapFilter);
  }

  private  Flux map(Map namedInputStreams, V providedRecord, Class providedRecordClass,
      Set triplesMapFilter) {
    var mappingContext = MappingContext.builder()
        .triplesMapFilter(triplesMapFilter)
        .triplesMappers(triplesMappers)
        .refObjectMapperToParentTriplesMapper(refObjectMapperToParentTriplesMapper)
        .build();

    return Flux.fromIterable(getSources(mappingContext, namedInputStreams, providedRecord, providedRecordClass))
        .flatMap(resolvedSourceEntry -> mapSource(mappingContext, resolvedSourceEntry))
        .concatWith(resolveJoins(mappingContext))
        .doOnTerminate(() -> triplesMappers.forEach(TriplesMapper::cleanup));
  }

  private  Set> getSources(MappingContext mappingContext,
      Map namedInputStreams, V providedRecord, Class providedRecordClass) {

    var logicalSourcesPerSource = mappingContext.getLogicalSourcesPerSource();

    if (providedRecord != null) {
      if (logicalSourcesPerSource.size() > 1) {
        throw new RmlMapperException(
            String.format("Multiple sources found when mapping provided record. This is not supported:%n%s",
                exception(logicalSourcesPerSource.values()
                    .stream()
                    .flatMap(Set::stream)
                    .collect(toSet()))));
      }

      return Set.of(ResolvedSource.of(Iterables.getFirst(logicalSourcesPerSource.keySet(), null), providedRecord,
          providedRecordClass));
    }

    return logicalSourcesPerSource.entrySet()
        .stream()
        .map(sourceEntry -> resolveSource(sourceEntry.getKey(), sourceEntry.getValue(), namedInputStreams))
        .collect(Collectors.toUnmodifiableSet());
  }

  private ResolvedSource resolveSource(Object source, Set inCaseOfException,
      Map namedInputStreams) {
    if (source instanceof NameableStream) {
      NameableStream stream = (NameableStream) source;
      String unresolvedName = stream.getStreamName();
      String name = StringUtils.isBlank(unresolvedName) ? DEFAULT_STREAM_NAME : unresolvedName;

      if (!namedInputStreams.containsKey(name)) {
        throw new RmlMapperException(String.format("Could not resolve input stream with name %s for logical source: %s",
            name, exception(Iterables.getFirst(inCaseOfException, null))));
      }

      return ResolvedSource.of(source, namedInputStreams.get(name), InputStream.class);
    }

    var resolved = sourceResolver.apply(source)
        .orElseThrow(() -> new RmlMapperException(String.format("Could not resolve source for logical source: %s",
            exception(Iterables.getFirst(inCaseOfException, null)))));

    return ResolvedSource.of(source, resolved, Object.class);
  }

  private Flux mapSource(MappingContext mappingContext, ResolvedSource resolvedSource) {
    return Flux.just(sourceToLogicalSourceResolver.get(resolvedSource.getRmlSource()))
        .flatMap(resolver -> resolver
            .getLogicalSourceRecords(mappingContext.logicalSourcesPerSource.get(resolvedSource.getRmlSource()))
            .apply(resolvedSource))
        .flatMap(logicalSourceRecord -> mapTriples(mappingContext, logicalSourceRecord));
  }

  private Flux mapTriples(MappingContext mappingContext, LogicalSourceRecord logicalSourceRecord) {
    return Flux.fromIterable(mappingContext.getTriplesMappersForLogicalSource(logicalSourceRecord.getLogicalSource()))
        .flatMap(triplesMapper -> triplesMapper.map(logicalSourceRecord));
  }

  private Flux resolveJoins(MappingContext mappingContext) {
    return Flux.fromIterable(mappingContext.getRefObjectMapperToParentTriplesMapper()
        .entrySet())
        .flatMap(romMapperPtMapper -> romMapperPtMapper.getKey()
            .resolveJoins(romMapperPtMapper.getValue()));
  }

  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @Getter
  static class MappingContext {
    private Set triplesMapFilter;

    private Map>> triplesMapperPerLogicalSource;

    private Map> logicalSourcesPerSource;

    private Map, TriplesMapper> refObjectMapperToParentTriplesMapper;

    public Set> getTriplesMappersForLogicalSource(LogicalSource logicalSource) {
      return triplesMapperPerLogicalSource.get(logicalSource);
    }

    static  MappingContext.Builder builder() {
      return new MappingContext.Builder<>();
    }

    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    static class Builder {
      private Set triplesMapFilter;

      private Set> triplesMappers;

      private Map, TriplesMapper> refObjectMapperToParentTriplesMapper;

      public MappingContext.Builder triplesMapFilter(Set triplesMapFilter) {
        this.triplesMapFilter = triplesMapFilter;
        return this;
      }

      public MappingContext.Builder triplesMappers(Set> triplesMappers) {
        this.triplesMappers = triplesMappers;
        return this;
      }

      public MappingContext.Builder refObjectMapperToParentTriplesMapper(
          Map, TriplesMapper> refObjectMapperToParentTriplesMapper) {
        this.refObjectMapperToParentTriplesMapper = refObjectMapperToParentTriplesMapper;
        return this;
      }

      public MappingContext build() {
        var actionableTriplesMaps = Mappings.filterMappable(triplesMappers.stream()
            .map(TriplesMapper::getTriplesMap)
            .filter(triplesMap -> triplesMapFilter.isEmpty() || triplesMapFilter.contains(triplesMap))
            .collect(toSet()));

        var filteredTriplesMappersPerLogicalSource = triplesMappers.stream()
            .filter(triplesMapper -> actionableTriplesMaps.contains(triplesMapper.getTriplesMap()))
            .collect(groupingBy(triplesMapper -> triplesMapper.getTriplesMap()
                .getLogicalSource(), toSet()));

        var filteredLogicalSourcesPerSource = filteredTriplesMappersPerLogicalSource.keySet()
            .stream()
            .collect(groupingBy(LogicalSource::getSource, toSet()));

        Map, TriplesMapper> filteredRefObjectMapperToParentTriplesMapper =
            refObjectMapperToParentTriplesMapper.entrySet()
                .stream()
                .filter(entry -> actionableTriplesMaps.contains(entry.getKey()
                    .getTriplesMap()))
                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));

        return new MappingContext<>(actionableTriplesMaps, filteredTriplesMappersPerLogicalSource,
            filteredLogicalSourcesPerSource, filteredRefObjectMapperToParentTriplesMapper);
      }
    }
  }
}