MapstructElasticMapper.java

/*
 * Copyright 2025 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.genesys.server.api.v2.mapper;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclAwareModel;
import org.genesys.blocks.util.JsonSidConverter.SidProvider;
import org.genesys.server.model.dataset.QDatasetAccessionRef;
import org.genesys.server.model.genesys.Accession;
import org.genesys.server.model.genesys.AccessionId;
import org.genesys.server.model.genesys.AccessionList;
import org.genesys.server.model.impl.QDiversityTreeAccessionRef;
import org.genesys.server.model.impl.QSubsetAccessionRef;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.MapperConfig;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import lombok.extern.slf4j.Slf4j;

import javax.validation.constraints.NotNull;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.FIELD, config = MapstructElasticMapper.DataMappingsConfig.class)
@Slf4j
public abstract class MapstructElasticMapper {

	@Autowired
	private SidProvider sidNameProvider;
	
	@Autowired
	private JPAQueryFactory jpaQueryFactory;

	// Configs and utils

	public <A, B> B[] map(@NotNull A[] source, @NotNull Function<A, B> mapper, @NotNull B[] array) {
		var res = new ArrayList<B>(source.length);
		for (A a : source) {
			res.add(mapper.apply(a));
		}
		return res.toArray(array);
	}

	public <A, B> List<B> map(@NotNull Collection<A> source, @NotNull Function<A, B> mapper) {
		if (source == null) return null;
		var res = new LinkedList<B>();
		source.forEach(x -> res.add(mapper.apply(x)));
		return res;
	}

	public <A, B> Page<B> map(@NotNull Page<A> source, @NotNull Function<A, B> mapper) {
		if (source == null) return null;
		return source.map(mapper);
	}

	@Named("getAclSid")
	public String getAclSid(Long id) {
		if (id == null) {
			// Don't write SID name
			return null;
		}
		return sidNameProvider == null ? id.toString() : sidNameProvider.getSidName(id);
	}

	@Named("isPublic")
	static boolean isPublic(AclAwareModel model) {
		if (model.getId() == null) {
			// Don't write permissions for non-persisted objects
			return false;
		}

		try {
			// StopWatch stopWatch = StopWatch.createStarted();
			var result = SecurityContextUtil.anyoneHasPermission(model, "READ");
			// log.warn("isPublic in {}ms", stopWatch.getTime());
			return result;
		} catch (Throwable e) {
			return false;
		}
	}

	@MapperConfig(imports = {
		Objects.class
	})
	interface DataMappingsConfig {

		@Retention(RetentionPolicy.CLASS)
		@Mapping(source = "createdBy", target = "createdBy", qualifiedByName = "getAclSid")
		@Mapping(source = "lastModifiedBy", target = "lastModifiedBy", qualifiedByName = "getAclSid")
		public @interface AuditNames {
		}

		@Retention(RetentionPolicy.CLASS)
		@Mapping(target = "createdBy", ignore = true)
		@Mapping(target = "lastModifiedBy", ignore = true)
		public @interface IgnoreAudit {
		}

		@Retention(RetentionPolicy.CLASS)
		@Mapping(target = "public", source = ".", qualifiedByName = "isPublic")
		public @interface IsPublic {
		}

		@Retention(RetentionPolicy.CLASS)
		@Mapping(source = ".", target = "_permissions", qualifiedByName = "getPermissions")
		public @interface IncludePermissions {
		}

		@Retention(RetentionPolicy.CLASS)
		@Mapping(target = "_permissions", ignore = true)
		public @interface IgnorePermissions {
		}
	}

	@Mapping(source = ".", target = "datasets", qualifiedByName = "mapDatasetUUIDs")
	@Mapping(source = ".", target = "subsets", qualifiedByName = "mapSubsetUUIDs")
	@Mapping(source = ".", target = "diversityTrees", qualifiedByName = "mapDiversityTreeUUIDs")
	public abstract org.genesys.server.model.elastic.AccessionDTO mapES(Accession accession);

	@Mapping(source = "lists", target = "lists", qualifiedByName = "mapAccessionListUUIDs")
	public abstract org.genesys.server.model.elastic.AccessionIdDTO mapES(AccessionId accession);

	@Named("mapDatasetUUIDs")
	protected List<UUID> mapDatasetUUIDs(Accession accession) {
		return jpaQueryFactory
			.from(QDatasetAccessionRef.datasetAccessionRef)
			.select(QDatasetAccessionRef.datasetAccessionRef.list().uuid)
			.where(QDatasetAccessionRef.datasetAccessionRef.accession().eq(accession))
			.fetch();
	}

	@Named("mapSubsetUUIDs")
	protected List<UUID> mapSubsetUUIDs(Accession accession) {
		return jpaQueryFactory
			.from(QSubsetAccessionRef.subsetAccessionRef)
			.select(QSubsetAccessionRef.subsetAccessionRef.list().uuid)
			.where(QSubsetAccessionRef.subsetAccessionRef.accession().eq(accession))
			.fetch();
	}

	@Named("mapDiversityTreeUUIDs")
	protected List<UUID> mapDiversityTreeUUIDs(Accession accession) {
		return jpaQueryFactory
			.from(QDiversityTreeAccessionRef.diversityTreeAccessionRef)
			.select(QDiversityTreeAccessionRef.diversityTreeAccessionRef.list().uuid)
			.where(QDiversityTreeAccessionRef.diversityTreeAccessionRef.accession().eq(accession))
			.fetch();
	}

	@Named("mapAccessionListUUIDs")
	protected Set<UUID> mapAccessionListUUIDs(Set<AccessionList> uuidModels) {
		return uuidModels.stream().map(AccessionList::getUuid).collect(Collectors.toSet());
	}
}