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

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

There is a newer version: 3.0.2
Show newest version
package io.katharsis.jpa;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;

import io.katharsis.jpa.internal.JpaRepositoryBase;
import io.katharsis.jpa.internal.JpaRepositoryUtils;
import io.katharsis.jpa.internal.meta.MetaAttribute;
import io.katharsis.jpa.internal.meta.MetaEntity;
import io.katharsis.jpa.internal.meta.MetaType;
import io.katharsis.jpa.internal.paging.DefaultPagedMetaInformation;
import io.katharsis.jpa.internal.paging.PagedMetaInformation;
import io.katharsis.jpa.mapping.IdentityMapper;
import io.katharsis.jpa.mapping.JpaMapper;
import io.katharsis.jpa.mapping.JpaMapping;
import io.katharsis.jpa.query.ComputedAttributeRegistry;
import io.katharsis.jpa.query.JpaQuery;
import io.katharsis.jpa.query.JpaQueryExecutor;
import io.katharsis.jpa.query.JpaQueryFactory;
import io.katharsis.jpa.query.Tuple;
import io.katharsis.queryspec.QuerySpec;
import io.katharsis.queryspec.QuerySpecBulkRelationshipRepository;
import io.katharsis.response.paging.PagedResultList;

public class JpaRelationshipRepository extends JpaRepositoryBase
		implements QuerySpecBulkRelationshipRepository {

	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 sourceResourceClass from this relation
	 * @param targetResourceClass from this relation
	 */
	public JpaRelationshipRepository(JpaModule module, Class sourceResourceClass, Class targetResourceClass) {
		super(module, targetResourceClass);
		this.sourceResourceClass = sourceResourceClass;

		JpaMapping sourceMapping = module.getMapping(sourceResourceClass);
		if (sourceMapping != null) {
			this.sourceEntityClass = sourceMapping.getEntityClass();
			this.sourceMapper = sourceMapping.getMapper();
		}
		else {
			this.sourceEntityClass = sourceResourceClass;
			this.sourceMapper = IdentityMapper.newInstance();
		}
		this.entityMeta = module.getMetaLookup().getMeta(sourceEntityClass).asEntity();
	}

	@Override
	public void setRelation(S source, J targetId, String fieldName) {
		checkUpdateable();
		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) {
			checkCreateable();

			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();
				checkDeleteable();
				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) {
				checkCreateable();
				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) {
		checkCreateable();
		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) {
		checkDeleteable();
		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 Map findOneTargets(Iterable sourceIds, String fieldName, QuerySpec querySpec) {
		checkReadable();
		return getUniqueOrNull(getResults(sourceIds, fieldName, querySpec));
	}

	@Override
	public Map> findManyTargets(Iterable sourceIds, String fieldName, QuerySpec querySpec) {
		checkReadable();
		return getResults(sourceIds, fieldName, querySpec);

	}

	private Map> getResults(Iterable sourceIds, String fieldName, QuerySpec querySpec) {
		resetEntityManager();
		
		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");
		}

		QuerySpec bulkQuerySpec = querySpec.duplicate();
		
		QuerySpec filteredQuerySpec = filterQuerySpec(bulkQuerySpec);

		JpaQueryFactory queryFactory = module.getQueryFactory();
		JpaQuery query = queryFactory.query(sourceEntityClass, fieldName, sourceIdLists);
		query.addParentIdSelection();
		query = filterQuery(filteredQuerySpec, query);

		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);

		List tuples = executor.getResultTuples();
		
		tuples = filterTuples(bulkQuerySpec, tuples);

		Map> map = mapTuples(tuples);

		// support paging for non-bulk requests
		if (sourceIdLists.size() == 1 && querySpec.getLimit() != null) {
			long totalRowCount = executor.getTotalRowCount();
			I sourceId = sourceIdLists.get(0);
			Iterable iterable = map.get(sourceId);
			if (iterable == null) {
				iterable = new ArrayList<>();
			}
			map.put(sourceId, new PagedResultList<>((List) iterable, totalRowCount));
		}

		resetEntityManager();
		return map;
	}

	@SuppressWarnings("unchecked")
	private Map> mapTuples(List tuples) {
		Map> map = new HashMap<>();
		for (Tuple tuple : tuples) {
			I sourceId = (I) tuple.get(0, Object.class);
			tuple.reduce(1);

			List iterable = (List) map.get(sourceId);
			if (iterable == null) {
				iterable = new ArrayList<>();
				map.put(sourceId, iterable);
			}
			iterable.add(mapper.map(tuple));
		}
		return map;
	}

	@Override
	public T findOneTarget(I sourceId, String fieldName, QuerySpec querySpec) {
		Map map = findOneTargets(Arrays.asList(sourceId), fieldName, querySpec);
		if (map.isEmpty()) {
			return null;
		}
		else if (map.containsKey(sourceId) && map.size() == 1) {
			return map.get(sourceId);
		}
		else {
			throw new IllegalStateException("expected sourceId=" + sourceId + "in result " + map);
		}
	}

	@Override
	public List findManyTargets(I sourceId, String fieldName, QuerySpec querySpec) {
		Map> map = findManyTargets(Arrays.asList(sourceId), fieldName, querySpec);
		if (map.isEmpty()) {
			return Collections.emptyList();
		}
		else if (map.containsKey(sourceId) && map.size() == 1) {
			return (List) map.get(sourceId);
		}
		else {
			throw new IllegalStateException("expected sourceId=" + sourceId + "in result " + map);
		}
	}

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

	@Override
	public Class getTargetResourceClass() {
		return resourceClass;
	}

	public Class getTargetEntityClass() {
		return entityClass;
	}

	@Override
	protected PagedMetaInformation newPagedMetaInformation() {
		return new DefaultPagedMetaInformation();
	}
}