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

com.xlrit.gears.server.graphql.ViewResolver Maven / Gradle / Ivy

The newest version!
package com.xlrit.gears.server.graphql;

import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.xlrit.gears.base.exception.AuthException;
import com.xlrit.gears.base.util.Range;
import com.xlrit.gears.engine.meta.*;
import com.xlrit.gears.engine.security.AuthManager;
import com.xlrit.gears.engine.snel.EvaluatorContext;
import com.xlrit.gears.engine.snel.EvaluatorScope;
import jakarta.persistence.EntityManager;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.query.AuditEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;

@Controller("graphqlViewResolver")
@RequiredArgsConstructor
public class ViewResolver {
	private static final Logger LOG = LoggerFactory.getLogger(ViewResolver.class);

	private final ViewManager viewManager;
	private final MetaManager metaManager;
	private final AuthManager authManager;
	private final EntityManager entityManager;

	// === top level === //

	@QueryMapping
	@PreAuthorize("isAuthenticated()")
	public MainListView list(@Argument String key) {
		LOG.debug("list(key={})", key);
		ViewManager.ViewDefinitions.ListDef listDef = viewManager.getListDef(key);
		if (listDef.hasRequiredRoles() && !authManager.hasAnyRole(listDef.getRoles())) {
			throw new AuthException("Not authorized to view list '" + key + "'");
		}
		return new MainListView(listDef);
	}

	@QueryMapping
	@PreAuthorize("isAuthenticated()")
	public DetailView detail(@Argument String key) {
		LOG.debug("detail(key={})", key);

		ViewManager.ViewDefinitions.DetailDef detailDef = viewManager.getDetailDef(key);
		if (detailDef.hasRequiredRoles() && !authManager.hasAnyRole(detailDef.getRoles())) {
			throw new AuthException("Not authorized to view detail '" + key + "'");
		}

		EntityInfo entityInfo = metaManager.requireEntityInfo(detailDef.getEntity());
		return new DetailView(detailDef, entityInfo);
	}

	// === main list view === //

	@SchemaMapping
	public Link link(MainListView listView, @Argument String key) {
		return listView.link(key);
	}

	@SchemaMapping
	public long count(MainListView listView, @Argument String filter) {
		return listView.count(filter);
	}

	@SchemaMapping
	public JsonNode data(MainListView listView, @Argument String filter, @Argument Range range) {
		LOG.debug("data(listView={})", listView.getId());
		return listView.data(filter, range);
	}

	// === nested list view === //

	@SchemaMapping
	public long count(DetailView.DetailInstance.NestedListView listView, @Argument String filter) {
		long result = listView.count(filter);
		LOG.debug("count for {}: result={}", listView, result);
		return result;
	}

	@SchemaMapping
	public JsonNode data(DetailView.DetailInstance.NestedListView listView, @Argument String filter, @Argument Range range) {
		LOG.debug("data for {}", listView);
		return listView.data(filter, range);
	}

	// === detail view === //

	@SchemaMapping
	public DetailView.DetailInstance instance(DetailView detailView, @Argument String id) {
		LOG.debug("instance(detailView={})", detailView.getId());
		return detailView.instance(id);
	}

	// === detail instance === //

	@SchemaMapping
	public DetailView.DetailInstance.NestedListView list(DetailView.DetailInstance instance, @Argument String key) {
		return instance.list(key);
	}

	@SchemaMapping
	public Link link(DetailView.DetailInstance instance, @Argument String key) {
		return instance.link(key);
	}

	// === helper === //

	private EvaluatorContext createEvaluatorContext(PrintOptions printOptions, EvaluatorScope scope) {
		Printer printer = metaManager.printer(printOptions);
		return new EvaluatorContext(printer, scope);
	}

	// === inner types === //

	public interface ListView {
		long count(String filter);
		JsonNode data(String filter, Range range);
	}

	@Data
	public class MainListView implements ListView {
		private final ViewManager.ViewDefinitions.ListDef listDef;

		public String getId() {
			return listDef.getKey();
		}

		public JsonNode getSchema() {
			return viewManager.listSchema(listDef);
		}

		public List getLinks() {
			return listDef.getLinks().stream()
				.map(linkDef -> new GenericLink(listDef.getFqk(), linkDef))
				.collect(Collectors.toList());
		}

		public Link link(String key) {
			return listDef.getLinks().stream()
				.filter(linkDef -> key.equals(linkDef.getKey()))
				.map(linkDef -> new GenericLink(listDef.getFqk(), linkDef))
				.findAny().orElseThrow();
		}

		public long count(String filter) {
			return viewManager.listCount(listDef, filter);
		}

		public JsonNode data(String filter, Range range) {
			return viewManager.listData(listDef, filter, range);
		}
	}

	@Data
	public class DetailView {
		private final ViewManager.ViewDefinitions.DetailDef detailDef;
		private final EntityInfo entityInfo;

		public String getId() {
			return detailDef.getKey();
		}

		public JsonNode getSchema() {
			return viewManager.detailSchema(detailDef);
		}

		public List getLinks() {
			return detailDef.getLinks().stream()
				.map(linkDef -> new GenericLink(detailDef.getFqk(), linkDef))
				.collect(Collectors.toList());
		}

		public DetailInstance instance(String id) {
			return new DetailInstance(id);
		}

		public boolean isAudited() {
			Class clz = entityInfo.getObjectClass();
			return AuditReaderFactory.get(entityManager).isEntityClassAudited(clz);
		}

		@Data
		public class DetailInstance {
			private final String id;
			private JsonNode mainData;   // initialized lazily
			private EvaluatorContext evaluatorCtx; // initialized lazily

			protected EvaluatorContext getEvaluatorCtx() {
				if (this.evaluatorCtx == null) {
					Printer printer = metaManager.printer(PrintOptions.DEFAULT);
					EvaluatorScope scope = viewManager.createEvaluatorScope(detailDef, id);
					this.evaluatorCtx = new EvaluatorContext(printer, scope);
				}
				return this.evaluatorCtx;
			}

			public JsonNode getData() {
				if (this.mainData == null) {
					LOG.debug("Loading data for detail view key={}, id={}", detailDef.getKey(), id);
					this.mainData = viewManager.detailData(detailDef, id);
				}
				return this.mainData;
			}

			public List getEnabledSectionKeys() {
				EvaluatorContext evaluatorCtx = getEvaluatorCtx();
				return detailDef.getSections().stream()
					.filter(section -> section.isEnabled(evaluatorCtx))
					.map(ViewManager.ViewDefinitions.DetailDef.SectionDef::getKey)
					.toList();
			}

			public NestedListView list(String sectionKey) {
				ViewManager.ViewDefinitions.DetailDef.SectionDef section = detailDef.getSection(sectionKey);
				return new NestedListView(section);
			}

			public List getLinks() {
				return detailDef.getLinks().stream()
					.map(linkDef -> new DetailLink(detailDef.getFqk(), linkDef))
					.collect(Collectors.toList());
			}

			public Link link(String key) {
				return detailDef.getLinks().stream()
					.filter(linkDef -> key.equals(linkDef.getKey()))
					.map(linkDef -> new DetailLink(detailDef.getFqk(), linkDef))
					.findAny().orElseThrow();
			}

			@SuppressWarnings("unchecked")
			public List getRevisions() {
				List revisions =
					AuditReaderFactory.get(entityManager).createQuery()
						.forRevisionsOfEntity(entityInfo.getObjectClass(), false)
						.add(AuditEntity.id().eq(id))
						.getResultList();

				LOG.info("Returning {} revisions for {} #{}", revisions.size(), entityInfo.getTypeName(), id);
				return revisions.stream().map(Revision::fromRevisionEntity).toList();
			}

			@Data
			public class NestedListView implements ListView {
				private final ViewManager.ViewDefinitions.DetailDef.SectionDef sectionDef;
				private final boolean disabled;

				public NestedListView(ViewManager.ViewDefinitions.DetailDef.SectionDef sectionDef) {
					this.sectionDef = sectionDef;
					this.disabled = sectionDef.isConditional() && sectionDef.isEnabled(getEvaluatorCtx()) != Boolean.TRUE;
				}

				@Override
				public long count(String filter) {
					if (filter != null && !filter.isBlank()) throw new RuntimeException("Filtering is currently not supported for nested lists");
					if (disabled) return -1;
					return viewManager.nestedListCount(detailDef, sectionDef, id);
				}

				@Override
				public JsonNode data(String filter, Range range) {
					if (disabled) return NullNode.getInstance();
					LOG.debug("Loading data for nested list view key={}, section={}, id={}", detailDef.getKey(), sectionDef.getKey(), id);
					return viewManager.nestedListData(detailDef, sectionDef, id, range);
				}

				@Override
				public String toString() {
					return "NestedListView[detail=" + detailDef.getKey() + ", sectionDef=" + sectionDef.getKey() + "]";
				}
			}

			public class DetailLink extends Link {
				public DetailLink(String parentFqk, ViewManager.ViewDefinitions.LinkDef linkDef) {
					super(parentFqk, linkDef);
				}

				@Override
				public Boolean getEnabled() {
					return linkDef.isEnabled(getEvaluatorCtx());
				}

				@Override
				public JsonNode getFormValues() {
					return viewManager.getFormValues(linkDef, getEvaluatorCtx());
				}
			}
		}
	}

	@RequiredArgsConstructor
	public abstract static class Link {
		protected final String parentFqk;
		protected final ViewManager.ViewDefinitions.LinkDef linkDef;

		public String getId() { return parentFqk + ":" + linkDef.getKey(); }
		public String getKey() { return linkDef.getKey(); }
		public String getRef() { return linkDef.getRef(); }
		public String getLabel() { return linkDef.getLabel(); }
		public String getIcon() { return linkDef.getIcon(); }
		public boolean isConditional() { return linkDef.isConditional(); }
		public String getProcessKey() { return linkDef.getProcessKey(); }
		public ViewManager.LinkKind getKind() { return linkDef.getKind(); }
		public abstract Boolean getEnabled();
		public abstract JsonNode getFormValues();
	}

	public class GenericLink extends Link {
		public GenericLink(String parentFqk, ViewManager.ViewDefinitions.LinkDef linkDef) {
			super(parentFqk, linkDef);
		}

		public Boolean getEnabled() {
			return isConditional() ? null : true; // null is unknown
		}

		@Override
		public JsonNode getFormValues() {
			EvaluatorContext evaluatorCtx = createEvaluatorContext(PrintOptions.DEFAULT, EvaluatorScope.ROOT);
			return viewManager.getFormValues(linkDef, evaluatorCtx);
		}
	}

	public record Revision(int rev, OffsetDateTime timestamp) {
		static Revision fromRevisionEntity(DefaultRevisionEntity r) {
			OffsetDateTime timestamp = OffsetDateTime.ofInstant(r.getRevisionDate().toInstant(), ZoneId.systemDefault());
			return new Revision(r.getId(), timestamp);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy