package fr.openwide.core.jpa.more.business.difference.service; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; import javax.annotation.PostConstruct; import org.bindgen.BindingRoot; import org.hibernate.proxy.HibernateProxy; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import de.danielbechler.diff.NodeQueryService; import de.danielbechler.diff.ObjectDiffer; import de.danielbechler.diff.ObjectDifferBuilder; import de.danielbechler.diff.differ.Differ; import de.danielbechler.diff.differ.DifferDispatcher; import de.danielbechler.diff.differ.DifferFactory; import de.danielbechler.diff.inclusion.Inclusion; import de.danielbechler.diff.inclusion.InclusionResolver; import de.danielbechler.diff.instantiation.TypeInfo; import de.danielbechler.diff.introspection.Introspector; import de.danielbechler.diff.introspection.StandardIntrospector; import de.danielbechler.diff.node.DiffNode; import de.danielbechler.diff.node.DiffNode.Visitor; import de.danielbechler.diff.node.Visit; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import fr.openwide.core.commons.util.binding.AbstractCoreBinding; import fr.openwide.core.commons.util.context.IExecutionContext.ITearDownHandle; import fr.openwide.core.commons.util.fieldpath.FieldPath; import fr.openwide.core.commons.util.fieldpath.FieldPathComponent; import fr.openwide.core.jpa.business.generic.model.GenericEntity; import fr.openwide.core.jpa.business.generic.model.GenericEntityReference; import fr.openwide.core.jpa.business.generic.service.IEntityService; import fr.openwide.core.jpa.business.generic.service.ITransactionScopeIndependantRunnerService; import fr.openwide.core.jpa.more.business.difference.differ.ExtendedCollectionDiffer; import fr.openwide.core.jpa.more.business.difference.differ.MultimapDiffer; import fr.openwide.core.jpa.more.business.difference.factory.DefaultHistoryDifferenceFactory; import fr.openwide.core.jpa.more.business.difference.factory.IHistoryDifferenceFactory; import fr.openwide.core.jpa.more.business.difference.inclusion.NonInheritingNodePathInclusionResolver; import fr.openwide.core.jpa.more.business.difference.model.Difference; import fr.openwide.core.jpa.more.business.difference.util.CompositeProxyInitializer; import fr.openwide.core.jpa.more.business.difference.util.DiffUtils; import fr.openwide.core.jpa.more.business.difference.util.IDifferenceFromReferenceGenerator; import fr.openwide.core.jpa.more.business.difference.util.IProxyInitializer; import fr.openwide.core.jpa.more.business.difference.util.TypeSafeBindingProxyInitializer; import fr.openwide.core.jpa.more.business.history.model.AbstractHistoryDifference; import fr.openwide.core.jpa.more.rendering.service.IRendererService; import fr.openwide.core.jpa.util.HibernateUtils; @SuppressFBWarnings("squid:S1226") public abstract class AbstractGenericEntityDifferenceServiceImpl<T extends GenericEntity<?, ?>> implements IDifferenceService<T> { @Autowired private IEntityService entityService; @Autowired private IRendererService rendererService; @Autowired private ITransactionScopeIndependantRunnerService transactionScopeIndependantRunnerService; @Autowired private DefaultHistoryDifferenceFactory<T> defaultHistoryDifferenceFactory; private IProxyInitializer<T> proxyInitializer; private Multimap<FieldPath, IHistoryDifferenceFactory<T>> specificHistoryDifferenceFactories; @SuppressWarnings("rawtypes") private List<Class<? extends GenericEntity>> genericEntityTypes; private IDifferenceFromReferenceGenerator<T> mainDifferenceGenerator; private IDifferenceFromReferenceGenerator<T> minimalDifferenceGenerator; @SuppressWarnings("unchecked") @PostConstruct protected void initialize() { this.genericEntityTypes = entityService.listAssignableEntityTypes(GenericEntity.class); this.mainDifferenceGenerator = new AbstractDifferenceFromReferenceGenerator() { @Override protected ObjectDiffer createDiffer() { return initializeDiffer(DiffUtils.builder()).build(); } }; this.minimalDifferenceGenerator = new AbstractDifferenceFromReferenceGenerator() { @Override protected ObjectDiffer createDiffer() { return initializeMinimalDiffer(DiffUtils.builder()).build(); } }; List<IProxyInitializer<? super T>> initializers = Lists.newArrayList(); // Initialization of the simple fields Iterable<? extends AbstractCoreBinding<? extends T, ?>> simpleFieldsBindingsList = getSimpleInitializationFieldsBindings(); initializers.add(new TypeSafeBindingProxyInitializer<T>(simpleFieldsBindingsList)); // Customized initializations Iterables.addAll(initializers, initializeInitializers()); this.proxyInitializer = new CompositeProxyInitializer<T>(initializers); // Customized creation of the HistoryDifference items ImmutableMultimap.Builder<FieldPath, IHistoryDifferenceFactory<T>> factoriesMapBuilder = ImmutableMultimap.builder(); Multimap<IHistoryDifferenceFactory<T>, FieldPath> specificHistoryDifferenceFactoriesToFieldPaths = getSpecificHistoryDifferenceFactories(); for (Entry<IHistoryDifferenceFactory<T>, FieldPath> entry : specificHistoryDifferenceFactoriesToFieldPaths.entries()) { factoriesMapBuilder.putAll(entry.getValue(), entry.getKey()); } specificHistoryDifferenceFactories = factoriesMapBuilder.build(); } protected Multimap<IHistoryDifferenceFactory<T>, FieldPath> getSpecificHistoryDifferenceFactories() { return ImmutableMultimap.<IHistoryDifferenceFactory<T>, FieldPath>of(); } protected abstract Iterable<? extends AbstractCoreBinding<? extends T, ?>> getSimpleInitializationFieldsBindings(); protected ObjectDifferBuilder initializeDiffer(ObjectDifferBuilder builder) { // Ignore Hibernate proxies fields builder = builder.introspection() .setDefaultIntrospector(new Introspector() { private Introspector delegate = new StandardIntrospector(); @Override public TypeInfo introspect(Class<?> type) { if (HibernateProxy.class.isAssignableFrom(type)) { type = type.getSuperclass(); } return delegate.introspect(type); } }) .and(); for (@SuppressWarnings("rawtypes") Class<? extends GenericEntity> clazz : genericEntityTypes) { builder = builder.inclusion().exclude() .propertyNameOfType(clazz, "new", "displayName", "id", "nameForToString") .and(); } builder = builder.differs().register(new DifferFactory() { @Override public Differ createDiffer(DifferDispatcher differDispatcher, NodeQueryService nodeQueryService) { ExtendedCollectionDiffer differ = new ExtendedCollectionDiffer(differDispatcher, nodeQueryService, nodeQueryService); return initializeCollectionDiffer(differ); } }); builder = builder.differs().register(new DifferFactory() { @Override public Differ createDiffer(DifferDispatcher differDispatcher, NodeQueryService nodeQueryService) { MultimapDiffer differ = new MultimapDiffer(differDispatcher, nodeQueryService, nodeQueryService); return initializeMultimapDiffer(differ); } }); return builder; } protected Iterable<? extends BindingRoot<? super T, ?>> getMinimalDifferenceFieldsBindings() { // By default, the minimal diff does not include any nodes return ImmutableList.<BindingRoot<? super T, ?>>of(); } protected final ObjectDifferBuilder initializeMinimalDiffer(ObjectDifferBuilder builder) { builder = initializeDiffer(builder); // Allows to include a node without having all its children included NonInheritingNodePathInclusionResolver parentInclusionResolver = new NonInheritingNodePathInclusionResolver(); builder = builder.inclusion().resolveUsing(parentInclusionResolver).and(); // We make sure, that if no nodes have been specified as included, all the other nodes won't be considered // as included "by default" builder = builder.inclusion().resolveUsing(new InclusionResolver() { @Override public Inclusion getInclusion(DiffNode node) { return Inclusion.DEFAULT; // Don't vote } @Override public boolean enablesStrictIncludeMode() { return true; } }).and(); for (BindingRoot<? super T, ?> binding : getMinimalDifferenceFieldsBindings()) { FieldPath path = FieldPath.fromBinding(binding); // The node and all its children are included builder = builder.inclusion().include().node(DiffUtils.toNodePath(path)) .and(); // For it to work, we also need to include the potential parents. // However we don't use the category system here nor the NodePathInclusionResolver because it would include // all the children of the parent (the categories are inherited by the children and the NodePathInclusionResolver // considers that we include all the children of a node. path = path.parent().get(); while (!path.isRoot()) { parentInclusionResolver.setInclusion(DiffUtils.toNodePath(path), Inclusion.INCLUDED); path = path.parent().get(); } } return builder; } protected Iterable<? extends IProxyInitializer<? super T>> initializeInitializers() { return Collections.emptyList(); } protected ExtendedCollectionDiffer initializeCollectionDiffer(ExtendedCollectionDiffer differ) { return differ; } protected MultimapDiffer initializeMultimapDiffer(MultimapDiffer differ) { return differ; } @Override public <HD extends AbstractHistoryDifference<HD, ?>> List<HD> toHistoryDifferences( final Supplier<HD> historyDifferenceSupplier, final Difference<T> rootDifference) { final Multimap<IHistoryDifferenceFactory<T>, DiffNode> factoriesToNodes = LinkedHashMultimap.create(); // Lists the leaf nodes and attributes the nodes to specific factories if needed. // A "leaf" is either a node without children or a node which is an element of a Collection or a Map. rootDifference.getDiffNode().visitChildren(new Visitor() { private Deque<FieldPathComponent> pathComponents = new LinkedList<>(); @Override public void node(DiffNode node, Visit visit) { visit.dontGoDeeper(); FieldPathComponent component = DiffUtils.toFieldPathComponent(node.getPath().getLastElementSelector()); pathComponents.addLast(component); Collection<IHistoryDifferenceFactory<T>> specificFactories = specificHistoryDifferenceFactories.get(FieldPath.of(pathComponents)); if (!specificFactories.isEmpty()) { for (IHistoryDifferenceFactory<T> specificFactory : specificFactories) { factoriesToNodes.put(specificFactory, node); } } else { if (component == FieldPathComponent.ITEM || !node.hasChildren()) { factoriesToNodes.put(defaultHistoryDifferenceFactory, node); } else { node.visitChildren(this); } } pathComponents.removeLast(); } }); final List<HD> historyDifferences = Lists.newLinkedList(); try (ITearDownHandle handle = rendererService.context().open()) { for (Map.Entry<IHistoryDifferenceFactory<T>, Collection<DiffNode>> entry : factoriesToNodes.asMap().entrySet()) { IHistoryDifferenceFactory<T> factory = entry.getKey(); Collection<DiffNode> nodes = entry.getValue(); historyDifferences.addAll(factory.create(historyDifferenceSupplier, rootDifference, nodes)); } } catch (RuntimeException e) { throw new IllegalStateException("Unexpected exception while computing HistoryDifferences", e); } return historyDifferences; } private abstract class AbstractDifferenceFromReferenceGenerator implements IDifferenceFromReferenceGenerator<T> { @Override public Difference<T> diff(T modified, T reference) { return new Difference<T>(reference, modified, createDiffer().compare(modified, reference)); } /** * Creates a differ. * <p> The differ must be instantiated each time we need it, as it's not thread safe. * <p> Moreover, it looks like it has an internal state which might not be cleaned up in case of errors. */ protected abstract ObjectDiffer createDiffer(); @Override public Difference<T> diffFromReference(T after) { T before = transactionScopeIndependantRunnerService.run(getReferenceProviderAndInitializer(after)); return diff(after, before); } private Callable<T> getReferenceProviderAndInitializer(T value) { final GenericEntityReference<?, T> reference = GenericEntityReference.ofUnknownIdType(value); return new Callable<T>() { @Override public T call() throws Exception { if (reference == null) { return null; } else { T databaseVersion = HibernateUtils.unwrap(entityService.getEntity(reference)); proxyInitializer.initialize(databaseVersion); return databaseVersion; } } }; } @Override public Callable<T> getReferenceProvider(T value) { final GenericEntityReference<?, T> reference = GenericEntityReference.ofUnknownIdType(value); return new Callable<T>() { @Override public T call() throws Exception { if (reference == null) { return null; } else { T databaseVersion = entityService.getEntity(reference); return HibernateUtils.unwrap(databaseVersion); } } }; } @Override public void initializeReference(T reference) { proxyInitializer.initialize(reference); } } @Override public IDifferenceFromReferenceGenerator<T> getMainDifferenceGenerator() { return mainDifferenceGenerator; } @Override public IDifferenceFromReferenceGenerator<T> getMinimalDifferenceGenerator() { return minimalDifferenceGenerator; } }