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

io.crnk.jpa.JpaRelationshipRepository Maven / Gradle / Ivy

package io.crnk.jpa;

import io.crnk.core.engine.information.resource.ResourceField;
import io.crnk.core.repository.RelationshipMatcher;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;

import io.crnk.core.engine.internal.utils.MultivaluedMap;
import io.crnk.core.engine.internal.utils.PreconditionUtil;
import io.crnk.core.queryspec.QuerySpec;
import io.crnk.core.repository.BulkRelationshipRepositoryV2;
import io.crnk.core.repository.RelationshipRepositoryV2;
import io.crnk.core.resource.list.ResourceList;
import io.crnk.core.resource.meta.HasMoreResourcesMetaInformation;
import io.crnk.core.resource.meta.MetaInformation;
import io.crnk.core.resource.meta.PagedMetaInformation;
import io.crnk.jpa.internal.JpaRepositoryBase;
import io.crnk.jpa.internal.JpaRepositoryUtils;
import io.crnk.jpa.internal.JpaRequestContext;
import io.crnk.jpa.mapping.JpaMapper;
import io.crnk.jpa.meta.MetaEntity;
import io.crnk.jpa.query.ComputedAttributeRegistry;
import io.crnk.jpa.query.JpaQuery;
import io.crnk.jpa.query.JpaQueryExecutor;
import io.crnk.jpa.query.JpaQueryFactory;
import io.crnk.jpa.query.Tuple;
import io.crnk.meta.model.MetaAttribute;
import io.crnk.meta.model.MetaType;

public class JpaRelationshipRepository extends JpaRepositoryBase
		implements RelationshipRepositoryV2, BulkRelationshipRepositoryV2 {

	private final ResourceField resourceField;

	private Class sourceResourceClass;

	private Class sourceEntityClass;

	private MetaEntity entityMeta;

	private JpaMapper sourceMapper;

	/**
	 * JPA relationship directly exposed as repository
	 *
	 * @param module that manages this repository
	 * @param resourceField from this relation
	 * @param repositoryConfig from this relation
	 */
	public JpaRelationshipRepository(JpaModule module, ResourceField resourceField, JpaRepositoryConfig
			repositoryConfig) {
		super(module, repositoryConfig);
		this.sourceResourceClass = (Class) resourceField.getParentResourceInformation().getResourceClass();
		this.resourceField = resourceField;

		JpaRepositoryConfig sourceMapping = module.getRepositoryConfig(sourceResourceClass);
		this.sourceEntityClass = sourceMapping.getEntityClass();
		this.sourceMapper = sourceMapping.getMapper();
		this.entityMeta = module.getJpaMetaProvider().getMeta(sourceEntityClass);
	}

	@Override
	public RelationshipMatcher getMatcher() {
		return new RelationshipMatcher().rule().field(resourceField).add();
	}

	@Override
	public void setRelation(S source, J targetId, String fieldName) {
		MetaAttribute attrMeta = entityMeta.getAttribute(fieldName);
		MetaAttribute oppositeAttrMeta = attrMeta.getOppositeAttribute();
		Class targetType = getElementType(attrMeta);

		Object sourceEntity = sourceMapper.unmap(source);

		EntityManager em = module.getEntityManager();
		Object target = targetId != null ? em.find(targetType, targetId) : null;
		attrMeta.setValue(sourceEntity, target);

		if (target != null && oppositeAttrMeta != null) {
			if (oppositeAttrMeta.getType().isCollection()) {
				oppositeAttrMeta.addValue(target, sourceEntity);
			}
			else {
				oppositeAttrMeta.setValue(target, sourceEntity);
			}
			em.persist(target);
		}
	}

	@Override
	public void setRelations(S source, Iterable targetIds, String fieldName) {
		MetaAttribute attrMeta = entityMeta.getAttribute(fieldName);
		MetaAttribute oppositeAttrMeta = attrMeta.getOppositeAttribute();
		Class targetType = getElementType(attrMeta);

		Object sourceEntity = sourceMapper.unmap(source);

		EntityManager em = module.getEntityManager();
		Collection targets = attrMeta.getType().asCollection().newInstance();
		for (J targetId : targetIds) {

			Object target = em.find(targetType, targetId);
			targets.add(target);
		}

		// detach current
		if (oppositeAttrMeta != null) {
			Collection col = (Collection) attrMeta.getValue(sourceEntity);
			Iterator iterator = col.iterator();
			while (iterator.hasNext()) {
				Object prevTarget = iterator.next();
				iterator.remove();
				if (oppositeAttrMeta.getType().isCollection()) {
					oppositeAttrMeta.removeValue(prevTarget, sourceEntity);
				}
				else {
					oppositeAttrMeta.setValue(prevTarget, null);
				}
			}
		}

		// attach new targets
		for (Object target : targets) {
			if (oppositeAttrMeta != null) {
				if (oppositeAttrMeta.getType().isCollection()) {
					oppositeAttrMeta.addValue(target, sourceEntity);
				}
				else {
					oppositeAttrMeta.setValue(target, sourceEntity);
				}
				em.persist(target);
			}
		}
		attrMeta.setValue(sourceEntity, targets);
	}

	private Class getElementType(MetaAttribute attrMeta) {
		MetaType type = attrMeta.getType();
		if (type.isCollection()) {
			return type.asCollection().getElementType().getImplementationClass();
		}
		else {
			return type.getImplementationClass();
		}
	}

	@Override
	public void addRelations(S source, Iterable targetIds, String fieldName) {
		MetaAttribute attrMeta = entityMeta.getAttribute(fieldName);
		MetaAttribute oppositeAttrMeta = attrMeta.getOppositeAttribute();
		Class targetType = getElementType(attrMeta);

		Object sourceEntity = sourceMapper.unmap(source);

		EntityManager em = module.getEntityManager();
		for (J targetId : targetIds) {
			Object target = em.find(targetType, targetId);
			attrMeta.addValue(sourceEntity, target);

			if (oppositeAttrMeta != null) {
				if (oppositeAttrMeta.getType().isCollection()) {
					oppositeAttrMeta.addValue(target, sourceEntity);
				}
				else {
					oppositeAttrMeta.setValue(target, sourceEntity);
				}
				em.persist(target);
			}
		}
		em.persist(sourceEntity);
	}

	@Override
	public void removeRelations(S source, Iterable targetIds, String fieldName) {
		MetaAttribute attrMeta = entityMeta.getAttribute(fieldName);
		MetaAttribute oppositeAttrMeta = attrMeta.getOppositeAttribute();
		Class targetType = getElementType(attrMeta);

		Object sourceEntity = sourceMapper.unmap(source);

		EntityManager em = module.getEntityManager();
		for (J targetId : targetIds) {
			Object target = em.find(targetType, targetId);
			attrMeta.removeValue(sourceEntity, target);

			if (target != null && oppositeAttrMeta != null) {
				if (oppositeAttrMeta.getType().isCollection()) {
					oppositeAttrMeta.removeValue(target, sourceEntity);
				}
				else {
					oppositeAttrMeta.setValue(target, null);
				}
			}
		}
	}

	@Override
	public MultivaluedMap findTargets(Iterable sourceIds, String fieldName, QuerySpec querySpec) {
		List sourceIdLists = new ArrayList<>();
		for (I sourceId : sourceIds) {
			sourceIdLists.add(sourceId);
		}

		if (querySpec.getLimit() != null && sourceIdLists.size() > 1) {
			throw new UnsupportedOperationException("page limit not supported for bulk inclusions");
		}
		// support paging for non-bulk requests
		boolean singleRequest = sourceIdLists.size() == 1;
		boolean pagedSingleRequest = singleRequest && querySpec.getLimit() != null;
		boolean fetchNext = pagedSingleRequest && isNextFetched(querySpec);

		QuerySpec bulkQuerySpec = querySpec.duplicate();

		QuerySpec filteredQuerySpec = filterQuerySpec(bulkQuerySpec);

		JpaQueryFactory queryFactory = module.getQueryFactory();
		JpaQuery query = queryFactory.query(sourceEntityClass, fieldName, sourceIdLists);
		query.setPrivateData(new JpaRequestContext(this, querySpec));
		query.addParentIdSelection();
		query = filterQuery(filteredQuerySpec, query);

		Class entityClass = repositoryConfig.getEntityClass();
		ComputedAttributeRegistry computedAttributesRegistry = queryFactory.getComputedAttributes();
		Set computedAttrs = computedAttributesRegistry.getForType(entityClass);

		JpaRepositoryUtils.prepareQuery(query, filteredQuerySpec, computedAttrs);

		JpaQueryExecutor executor = query.buildExecutor();
		JpaRepositoryUtils.prepareExecutor(executor, filteredQuerySpec, fetchRelations(fieldName));
		executor = filterExecutor(filteredQuerySpec, executor);
		if (fetchNext) {
			executor.setLimit(executor.getLimit() + 1);
		}


		List tuples = executor.getResultTuples();
		Boolean hasNext = null;
		if (fetchNext) {
			hasNext = tuples.size() == querySpec.getLimit() + 1;
			if (hasNext) {
				tuples = tuples.subList(0, querySpec.getLimit().intValue());
			}
		}

		tuples = filterTuples(bulkQuerySpec, tuples);

		MultivaluedMap map = mapTuples(tuples);

		if (singleRequest) {
			I sourceId = sourceIdLists.get(0);

			ResourceList iterable;
			if (map.containsKey(sourceId)) {
				iterable = (ResourceList) map.getList(sourceId);
			}
			else {
				iterable = repositoryConfig.newResultList();
				map.set(sourceId, iterable);
			}

			if (pagedSingleRequest) {
				MetaInformation metaInfo = iterable.getMeta();
				boolean fetchTotal = isTotalFetched(filteredQuerySpec);
				if (fetchTotal) {
					long totalRowCount = executor.getTotalRowCount();
					((PagedMetaInformation) metaInfo).setTotalResourceCount(totalRowCount);
				}
				if (fetchNext) {
					((HasMoreResourcesMetaInformation) metaInfo).setHasMoreResources(hasNext);
				}
			}
		}

		return map;
	}

	@SuppressWarnings("unchecked")
	private MultivaluedMap mapTuples(List tuples) {
		MultivaluedMap map = new MultivaluedMap() {

			@Override
			protected List newList() {
				return repositoryConfig.newResultList();
			}
		};
		for (Tuple tuple : tuples) {
			I sourceId = (I) tuple.get(0, Object.class);
			tuple.reduce(1);
			JpaMapper mapper = repositoryConfig.getMapper();
			map.add(sourceId, mapper.map(tuple));
		}
		return map;
	}

	@Override
	public T findOneTarget(I sourceId, String fieldName, QuerySpec querySpec) {
		MultivaluedMap map = findTargets(Arrays.asList(sourceId), fieldName, querySpec);
		if (!map.containsKey(sourceId)) {
			return null;
		}
		List list = map.getList(sourceId);
		return list.isEmpty() ? null : map.getUnique(sourceId);
	}

	@Override
	public ResourceList findManyTargets(I sourceId, String fieldName, QuerySpec querySpec) {
		MultivaluedMap map = findTargets(Arrays.asList(sourceId), fieldName, querySpec);
		PreconditionUtil.assertTrue("result must always include request for single element", map.containsKey(sourceId));
		return (ResourceList) map.getList(sourceId);
	}

	@Override
	public Class getSourceResourceClass() {
		return sourceResourceClass;
	}

	@Override
	public Class getTargetResourceClass() {
		return repositoryConfig.getResourceClass();
	}

	public Class getTargetEntityClass() {
		return repositoryConfig.getEntityClass();
	}
}