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

io.katharsis.jpa.internal.JpaResourceInformationBuilder Maven / Gradle / Ivy

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

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.ElementCollection;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OptimisticLockException;

import com.fasterxml.jackson.databind.JsonNode;

import io.katharsis.jpa.internal.meta.MetaAttribute;
import io.katharsis.jpa.internal.meta.MetaElement;
import io.katharsis.jpa.internal.meta.MetaEntity;
import io.katharsis.jpa.internal.meta.MetaKey;
import io.katharsis.jpa.internal.meta.MetaLookup;
import io.katharsis.jpa.internal.meta.MetaType;
import io.katharsis.request.dto.DataBody;
import io.katharsis.resource.field.ResourceAttributesBridge;
import io.katharsis.resource.field.ResourceField;
import io.katharsis.resource.field.ResourceField.LookupIncludeBehavior;
import io.katharsis.resource.information.AnnotationResourceInformationBuilder.AnnotatedResourceField;
import io.katharsis.resource.information.DefaultResourceInstanceBuilder;
import io.katharsis.resource.information.ResourceInformation;
import io.katharsis.resource.information.ResourceInformationBuilder;
import io.katharsis.resource.information.ResourceInstanceBuilder;

/**
 * Extracts resource information from JPA and Katharsis annotations. Katharsis
 * annotations take precedence.
 */
public class JpaResourceInformationBuilder implements ResourceInformationBuilder {

	private static final String ENTITY_NAME_SUFFIX = "Entity";

	private EntityManager em;
	private Set> exposedEntityClasses;
	private MetaLookup metaLookup;

	public JpaResourceInformationBuilder(MetaLookup metaLookup, EntityManager em,
			Set> exposedEntityClasses) {
		this.em = em;
		this.exposedEntityClasses = exposedEntityClasses;
		this.metaLookup = metaLookup;
	}

	@Override
	public boolean accept(Class resourceClass) {
		// needs to be configured for being exposed
		if (!exposedEntityClasses.contains(resourceClass)) {
			return false;
		}

		// needs to be an entity
		MetaElement meta = metaLookup.getMeta(resourceClass);
		if (meta instanceof MetaEntity) {
			MetaEntity metaEntity = meta.asEntity();
			MetaKey primaryKey = metaEntity.getPrimaryKey();
			return primaryKey != null && primaryKey.getElements().size() == 1;
		} else {
			return false;
		}
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public ResourceInformation build(final Class resourceClass) {
		String resourceType = getResourceType(resourceClass);

		MetaEntity meta = metaLookup.getMeta(resourceClass).asEntity();
		ResourceField idField = getIdField(meta);
		Set attributeFields = getAttributeFields(meta, false);
		Set relationshipFields = getAttributeFields(meta, true);
		Set ignoredFields = getIgnoredFields(meta);

		// make sure that existing managed object are used where available
		JpaResourceInstanceBuilder instanceBuilder = new JpaResourceInstanceBuilder(resourceClass);

		return new JpaResourceInformation(meta, resourceClass, resourceType, instanceBuilder, idField,
				new ResourceAttributesBridge(attributeFields, resourceClass), relationshipFields, ignoredFields);
	}

	class JpaResourceInstanceBuilder extends DefaultResourceInstanceBuilder {
		private MetaEntity meta;

		public JpaResourceInstanceBuilder(Class resourceClass) {
			super(resourceClass);
			meta = metaLookup.getMeta(resourceClass).asEntity();
		}

		@SuppressWarnings("unchecked")
		@Override
		public T buildResource(DataBody body) {
			String strId = body.getId();

			// use managed entities on the server-side
			if (strId != null && em != null) {
				Object id = meta.getPrimaryKey().fromKeyString(strId);
				Object entity = em.find(meta.getImplementationClass(), id);
				if (entity != null) {
					// version check
					checkOptimisticLocking(entity, body);
					return (T) entity;
				}
			}
			return super.buildResource(body);
		}

		private void checkOptimisticLocking(Object entity, DataBody body) {
			MetaAttribute versionAttr = meta.getVersionAttribute();
			if (versionAttr != null) {
				JsonNode versionNode = body.getAttributes().get(versionAttr.getName());
				if (versionNode != null) {
					Object requestVersion = versionAttr.getType().fromString(versionNode.asText());
					Object currentVersion = versionAttr.getValue(entity);
					if (!currentVersion.equals(requestVersion))
						throw new OptimisticLockException(
								body.getId() + " changed from version " + requestVersion + " to " + currentVersion);
				}
			}
		}

		@Override
		public int hashCode() {
			return super.hashCode() | meta.getName().hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			return super.equals(obj) && obj instanceof JpaResourceInstanceBuilder;
		}
	}

	class JpaResourceInformation extends ResourceInformation {

		private MetaEntity meta;
		private Set ignoredFields;

		public JpaResourceInformation(MetaEntity meta, Class resourceClass, String resourceType,
				ResourceField idField, ResourceAttributesBridge attributeFields,
				Set relationshipFields, Set ignoredFields) {
			this(meta, resourceClass, resourceType, null, idField, attributeFields, relationshipFields, ignoredFields,
					null, null);
		}

		public JpaResourceInformation(MetaEntity meta, Class resourceClass, String resourceType, // NOSONAR
				ResourceInstanceBuilder instanceBuilder, ResourceField idField,
				ResourceAttributesBridge attributeFields, Set relationshipFields,
				Set ignoredFields) {
			this(meta, resourceClass, resourceType, instanceBuilder, idField, attributeFields, relationshipFields,
					ignoredFields, null, null);
		}

		public JpaResourceInformation(MetaEntity meta, Class resourceClass, String resourceType, // NOSONAR
				ResourceInstanceBuilder instanceBuilder, ResourceField idField,
				ResourceAttributesBridge attributeFields, Set relationshipFields,
				Set ignoredFields, String metaFieldName, String linksFieldName) {
			super(resourceClass, resourceType, instanceBuilder, idField, attributeFields, relationshipFields,
					metaFieldName, linksFieldName);
			this.meta = meta;
			this.ignoredFields = ignoredFields;
		}

		/**
		 * Specialized ID handling to take care of embeddables and compound
		 * primary keys.
		 */
		@Override
		public Serializable parseIdString(String id) {
			return (Serializable) meta.getPrimaryKey().fromKeyString(id);
		}

		/**
		 * Specialized ID handling to take care of embeddables and compound
		 * primary keys.
		 */
		@Override
		public String toIdString(Object id) {
			return meta.getPrimaryKey().toKeyString(id);
		}

		@Override
		public Set getNotAttributeFields() {
			Set notAttributeFields = super.getNotAttributeFields();
			notAttributeFields.addAll(ignoredFields);
			return notAttributeFields;
		}

		@Override
		public int hashCode() {
			return super.hashCode() | ignoredFields.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			return super.equals(obj) && obj instanceof JpaResourceInformation;
		}
	}

	protected String getResourceType(Class entityClass) {
		String name = entityClass.getSimpleName();
		if (name.endsWith(ENTITY_NAME_SUFFIX)) {
			name = name.substring(0, name.length() - ENTITY_NAME_SUFFIX.length());
		}
		return Character.toLowerCase(name.charAt(0)) + name.substring(1);
	}

	protected Set getAttributeFields(MetaEntity meta, boolean relations) {
		Set fields = new HashSet<>();

		MetaAttribute primaryKeyAttr = JpaRepositoryUtils.getPrimaryKeyAttr(meta);
		for (MetaAttribute attr : meta.getAttributes()) {
			if (attr.equals(primaryKeyAttr) || attr.isAssociation() != relations || isIgnored(attr))
				continue;

			fields.add(toField(attr));
		}

		return fields;
	}

	protected Set getIgnoredFields(MetaEntity meta) {
		Set fields = new HashSet<>();
		for (MetaAttribute attr : meta.getAttributes()) {
			if (isIgnored(attr)) {
				fields.add(attr.getName());
			}
		}
		return fields;
	}

	protected boolean isIgnored(MetaAttribute attr) {
		MetaType type = attr.getType();
		if (type.isCollection()) {
			type = type.asCollection().getElementType();
		}
		return attr.isAssociation() && !exposedEntityClasses.contains(type.getImplementationClass());
	}

	protected ResourceField toField(MetaAttribute attr) {
		String jsonName = attr.getName();
		String underlyingName = attr.getName();
		Class type = attr.getType().getImplementationClass();
		Type genericType = attr.getType().getImplementationType();

		Class beanClass = attr.getParent().getImplementationClass();
		Field declaredField;
		try {
			declaredField = beanClass.getDeclaredField(attr.getName());
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
		List annotations = Arrays.asList(declaredField.getAnnotations());

		// use JPA annotations as default
		boolean lazyDefault = false;
		for (Annotation annotation : annotations) {
			if (annotation instanceof ElementCollection) {
				lazyDefault = ((ElementCollection) annotation).fetch() == FetchType.LAZY;
			} else if (annotation instanceof ManyToOne) {
				lazyDefault = ((ManyToOne) annotation).fetch() == FetchType.LAZY;
			} else if (annotation instanceof OneToMany) {
				lazyDefault = ((OneToMany) annotation).fetch() == FetchType.LAZY;
			} else if (annotation instanceof ManyToMany) {
				lazyDefault = ((ManyToMany) annotation).fetch() == FetchType.LAZY;
			}
		}

		// read Katharsis annotations
		boolean lazy = AnnotatedResourceField.isLazy(annotations, lazyDefault);
		boolean includeByDefault = AnnotatedResourceField.getIncludeByDefault(annotations);
		
		// related repositories should lookup, we ignore the hibernate proxies
		LookupIncludeBehavior lookupIncludeBehavior = AnnotatedResourceField.getLookupIncludeBehavior(annotations, LookupIncludeBehavior.AUTOMATICALLY_ALWAYS);
		return new ResourceField(jsonName, underlyingName, type, genericType, lazy, includeByDefault,
				lookupIncludeBehavior);
	}

	protected ResourceField getIdField(MetaEntity meta) {
		MetaAttribute attr = JpaRepositoryUtils.getPrimaryKeyAttr(meta);
		return toField(attr);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy