Lots of @AnyDiscriminatorValue

Hello,

In a hundred objects I have the use of an Action object collection

The Action object can only be referenced by a single object, so we use a foreign key with the role in the TACTION table

As linked entities can be of any type we are forced to use an IPossedeAction interface and the use of @Any for hibernate persistence.

But it forces to define more than 100 @AnyDiscriminatorValue

public class Materiel

  @OneToMany(fetch = LAZY, cascade = ALL)
  @JoinColumn(name = "OBJETMAITRE_ID") // must be delete for use "mappedby" with @any
  private Set<Action> actions = new HashSet<>();

public class Action
  @Any(fetch = LAZY)
  @AnyKeyJavaClass(String.class)
  @AnyDiscriminatorValue(discriminator = "com.hermes.ref.affaireaction.businessobject.AffaireEcore", entity = AffaireEcore.class)
  @AnyDiscriminatorValue(discriminator = "com.hermes.ref.acteur.businessobject.ActeurEcore", entity = ActeurEcore.class)
...
  @JoinColumn(name = "OBJETMAITRE_ID")
  @Column(name = "OBJETMAITRE_ROLE")
  private IPossedeActions objetMaitre;

About ten objects contain an action list, but they themselves are broken down into numerous sub-objects

Example, Materiel has 35 child classes

How can we make it easier than setting 100 @AnyDiscriminatorValue?

Currently I’m trying via an Integrator to add all the @Entites that implement this interface but I’m still manipulating the metadata…

I don’t think there is an easy way to do this, but do you really need this to be an association? Can’t you just store the primary key as Long and a Class:

  @Column(name = "OBJETMAITRE_ID")
  private Long objetMaitreId;

  @Column(name = "OBJETMAITRE_ROLE")
  private Class<?> objetMaitreRole;

  public void setObjetMaitre(IPossedeActions objetMaitre) {
    objetMaitreId = objetMaitre.getId();
    objetMaitreRole = objetMaitre.getClass();
  }

Then, when you need access to the object, just do session.find(objetMaitreRole, objetMaitreId)

1 Like

Thank you for the answer.

It’s a good idea, which I will consider.

But currently we are obliged to keep backward compatibility with the old proprietary ORM.

I will do this when we are in full hibernate.

But at least I have the information that there is not a simple solution to do this.

Hi,

I have a solution for automatically add the @AnyDiscriminatorValue with a Integrator.
Even if the idea is to simplify the model and use this mechanism very little

I must :

  1. get package-info files (i don’t’ found the information in the integrator argument)
public class EfluidScanner extends StandardScanner {

  public static final Set<String> packageInfos = new HashSet<>();

  @Override
  public ScanResult scan(ScanEnvironment environment, ScanOptions options, ScanParameters parameters) {
    ScanResult scan = super.scan(environment, options, parameters);
    scan.getLocatedPackages().forEach(packageDescriptor -> packageInfos.add(packageDescriptor.getName()));
    return new EfluidScanResult(scan);
  }
}
  1. which contains the custom annotation @AnyInterfaces for determining the interface to exploited (type of attribut annoted with @Any)
@Target({ PACKAGE })
@Retention(RUNTIME)
public @interface AnyInterfaces {
  // Liste des interfaces utilisées par des attributs annotés @Any
  Class<?>[] interfaces();
}

package-info.java

@AnyInterfaces(interfaces = {
    IActeurInterne.class,
    IEntiteAdministrative.class,
    IBusinessObjectParametrable.class,
    IObjetDestinataireEdition.class,
    BusinessObject.class,
    HermesBusinessObject.class,}
)
package com.efluid.hibernate.core.scanner;

import com.imrglobal.framework.businessObject.BusinessObject;
import com.hermes.arc.commun.businessobject.HermesBusinessObject;
import com.hermes.arc.edition.businessobject.IObjetDestinataireEdition;
import com.hermes.arc.habilitation.businessobject.IActeurInterne;
import com.hermes.arc.habilitation.businessobject.IEntiteAdministrative;
import com.hermes.arc.modeleobjetmetier.businessobject.IBusinessObjectParametrable;
  1. The integrator
    3.1 List the interfaces
    3.2 List the entity which implements interface
    3.3 Add all implementation, same with @AnyDiscriminatorValue, to @Any attributes which returns a class with the interface
public class AnyDiscriminatorValueIntegrator implements Integrator {

  private static final Logger LOG = LoggerFactory.getLogger(AnyDiscriminatorValueIntegrator.class);
  private static final String SEPARATOR = "::";
  // Interfaces @Any
  private Set<Class<?>> interfaces = new HashSet<>();
  // Entités qui implémentent les interfaces @Any.
  private Map<String, List<String>> implementationsInterface = new HashMap<>();

  @Override
  public void integrate(Metadata metadata, BootstrapContext bootstrapContext, SessionFactoryImplementor sessionFactory) {
    recuperationInterfaceAnySurPackageInfo(bootstrapContext);
    metadata.getEntityBindings().forEach(this::identifierEntiteImplementInterfaces);
    LOG.debug("Implémentations des interfaces {}", implementationsInterface);
    metadata.getEntityBindings().forEach(this::addAnyDiscriminatorValues);
  }

  private void recuperationInterfaceAnySurPackageInfo(BootstrapContext bootstrapContext) {
    for (String nomPackage : EfluidScanner.packageInfos) {
      AnyInterfaces annotation = getAnnotationAnyInterfaces(bootstrapContext, nomPackage);
      if (annotation != null) {
        for (Class<?> anInterface : annotation.interfaces()) {
          interfaces.add(anInterface);
          implementationsInterface.put(anInterface.getName(), new ArrayList());
        }
      }
    }
  }

  private void identifierEntiteImplementInterfaces(PersistentClass persistentClass) {
    for (Class anInterface : interfaces) {
      if (anInterface.isAssignableFrom(persistentClass.getMappedClass())) {
        DiscriminatorValue annotationDiscriminatorValue = persistentClass.getMappedClass().getAnnotation(DiscriminatorValue.class);
        implementationsInterface.get(anInterface.getName())
                                .add(persistentClass.getClassName()
                                         + SEPARATOR
                                         + (annotationDiscriminatorValue != null ? annotationDiscriminatorValue.value() : persistentClass.getClassName()));
      }
    }
  }

  private void addAnyDiscriminatorValues(PersistentClass persistentClass) {
    persistentClass.getProperties().stream().filter(property -> property.getValue() instanceof Any).forEach(this::addAnyDiscriminatorValue);
  }

  private static AnyInterfaces getAnnotationAnyInterfaces(BootstrapContext bootstrapContext, String nomPackage) {
    ClassLoaderService classLoaderService = bootstrapContext.getServiceRegistry().getService(ClassLoaderService.class);
    Package packaze = classLoaderService.packageForNameOrNull(nomPackage);
    XPackage pckg = bootstrapContext.getReflectionManager().toXPackage(packaze);
    return pckg.getAnnotation(AnyInterfaces.class);
  }

  private void addAnyDiscriminatorValue(Property propriete) {
    Any any = (Any) propriete.getValue();
    Map<Object, String> metaValues = any.getMetaValues();
    String typeRetour = (String) any.getDiscriminatorDescriptor().getTypeParameters().get(DynamicParameterizedType.RETURNED_CLASS);
    List<String> implementations = implementationsInterface.get(typeRetour);
    if (implementations != null) {
      implementations.forEach(entite -> metaValues.put(entite.split(SEPARATOR)[1], entite.split(SEPARATOR)[0]));
    } else if (metaValues.isEmpty()) {
      LOG.error("""
                    L'interface %s pour l'attribut %s#%s annoté @Any 
                    n'est pas déclarée dans une annotation AnyDiscriminatorValueIntegrator#interfaces
                    et ne possède pas de @AnyDiscriminatorValue.
                    """.formatted(typeRetour, propriete.getPersistentClass().getClassName(), propriete.getName()));
    }
  }

  @Override
  public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    LOG.debug("Exécuté lors d'une exception");
  }
}