Is there a way configure "Entity log by default?"

I’m not sure why one would want an entity NOT logged, so I’m wondering if I’m missing a setting somewhere that would cause entities to be logged by default as if I’d picked Auto/all in the admin panel?

Having to manually add new entities, and also go back and re-set “all” on ones with added attributes is a bit cumbersome.

Hi @jon.craig,
We had exactly the same need. I’ll share how we implemented it.

First, some context. We noticed that when we manually configure entity log, 2 entities were involved: LoggedEntity and LoggedAttribute.

So we implemented the following: On application start, there is a service that identify if any of our own application entity is not already configured to use entity log (LoggedEntity and LoggedAttribute records) and if not, create the corresponding records.

Just an example to help you implement it:

List<MetaClass> metaClasses = this.getMetaClassesOfType(StandardEntity.class, "com.example");
metaClasses.forEach(metaClass -> {
    // create LoggedEntity and LoggedAttribute if they are not already created
});
public List<MetaClass> getMetaClassesOfType(Class classType, String basePackage) {
	// Find entities based on provided classType
	ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
	provider.addIncludeFilter(new AssignableTypeFilter(classType));
	Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);

	// Get entities names
	List<String> beanNames = beans.stream().map(BeanDefinition::getBeanClassName).collect(Collectors.toList());

	// Return a list of the entities' metaclasses
	return this.getAllMetaClasses().stream().filter(metaClass -> beanNames.contains(metaClass.getJavaClass().getName())).collect(Collectors.toList());
}

Hope it helps.

Regards,
Peterson.

Hmmm, that’s pretty cumbersome but it may be what has to be done. Too bad there’s not just an @annotation one could put on an entity class to make it happen.

Would you mind sharing more of the code/etc? I’m not even sure how to hook into app startup and such; it’s not clear from that link. Plus how to enumerate the attributes of an entity and so on.

You’d think this would be part of the framework!

Hi @jon.craig,
Unfortunately, I’m on vacation at home (quarantine - Covid19) and I don’t have access to the code.

But I did an experiment that I think is better than the solution I implemented before. I created a fork of cuba-petclinic project. Take a look at this commit.

Summary of changes:

  1. Extend default EntityLog bean using Extending Business Logic.

  2. Setup entity log in memory for all entities of the project. You may wish to change the filter used to implement a different logic (classes using a given annotation, as you suggested, for example).

// Enable entity log for all application entities by default
metadata.getTools().getAllPersistentMetaClasses().stream().filter(metaClass -> {
	// Filter for application Persistent MetaClasses
	return metaClass.getJavaClass().getName().startsWith("com.haulmont.sample.petclinic");
}).forEach(metaClass -> {
	// Enable entity log by default
	Set<String> props = metaClass.getProperties().stream().map(metaProperty -> metaProperty.getName()).collect(Collectors.toSet());
	entitiesAuto.put(metaClass.getName(), props);
	entitiesManual.put(metaClass.getName(), props);
});

Hope it helps.

Regards,
Peterson.

Hey, cool, thank you. I’ll take a look at this. I’m playing with a solution based on your previous post but maybe this is better.

We have no hard quarantines yet… Take care, and thanks again.

@peterson.br a million thank yous to you! I implemented it as an ExtEntityLog like you showed, with additional filtering - I filter on the Entity name prefix to filter out the MappedSuperclass ones (they don’t need added to the log) and also added filtering out of the system properties (createdBy, createdTs, etc).

It works very well.

The CUBA team should consider adding this as an option or building in an @Annotation to use to mark entities as needing logged.

    private List<String> systemProperties = Arrays.asList(
            "createdBy",
            "createTs",
            "deletedBy",
            "deleteTs",
            "updatedBy",
            "updateTs",
            "id",
            "version"
    );
    private Set<String> getNonSystemProperties(MetaClass metaClass) {
        return metaClass.getProperties().stream().map(MetaProperty::getName).filter(mp -> !systemProperties.contains(mp)).collect(Collectors.toSet());
    }
    protected void loadEntities() {
        super.loadEntities();

        List<MetaClass> metaClasses = getMetaClassesOfType(StandardEntity.class, "com.medflex", "medflexj");

        metaClasses.forEach(mc -> {
            Set<String> props = getNonSystemProperties(mc);
            entitiesAuto.put(mc.getName(), props);
            entitiesManual.put(mc.getName(), props);
        });

        theLog.info("#### All entities added to log automatically ####");
    }

    private List<MetaClass> getMetaClassesOfType(Class classType, String basePackage, String entityPrefix) {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new AssignableTypeFilter(classType));
        Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
        List<String> beanNames = beans.stream().map(BeanDefinition::getBeanClassName).collect(Collectors.toList());

        // entityprefix lets us filter out mappedsuperclasses as they do not have the prefix
        return getAllMetaClasses().stream().filter(metaClass -> beanNames.contains(metaClass.getJavaClass().getName())).filter(mc -> mc.getName().startsWith(entityPrefix + "_")).collect(Collectors.toList());
    }

    private List<MetaClass> getAllMetaClasses() {
        Session session = metadata.getSession();
        return new ArrayList<>(session.getClasses());
    }
1 Like

Guys, I don’t get the purpose of classpath scanning here. Filtering of MetadataTools.getAllPersistentMetaClasses() by entity name prefix or by Java class package should be enough…

Hehe - don’t look at me, I’m very new to this and just adapted his example for my own purposes. I’ll try your suggestion; perhaps it’ll be a more elegant solution - though this approach definitely works.

Hi @knstvk,

Indeed it is not needed. In my first answer to the topic I’ve shared a part of a solution I’ve used when I started working with cuba (~1-1,5y ago), and I probably didn’t know cuba metadata very well, or there is another requirement that we had that I don’t remember (I don’t have access to the code right now).

In the second answer I provided a quick experiment I’ve done to help @jon.craig, this time using only what is required (only cuba metadata classes).

In the final solution, @jon.craig mixed a bit of both, that is why classpath scanning is being used.

It will be way easier to maintain the code :slight_smile:

I myself am thinking in refactoring the code we use after I return to work, because this solution is way better than the one that we implemented some time ago.

Regards,
Peterson.

1 Like

In case anyone wants to implement this (or, hell, if the CUBA team wants to just use the code), here is the full code for a working auto-entity-logger. It’s a bit kludgy in that you set up the entities you DON’T want logged in an array, but… it works. :wink: Thanks again to @peterson.br for the initial suggestions/code/help and @knstvk for the suggestion for making it even better.

package com.medflex.medflexj.core.security;

import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.cuba.core.global.Configuration;
import com.haulmont.cuba.security.app.EntityLog;
import org.slf4j.Logger;

import javax.inject.Inject;
import java.util.*;
import java.util.stream.Collectors;

// this is mostly from peterson.br @ CUBA forums.  Conversion to using getAllPersistentMetaClasses from
// knstvk @ CUBA forums.

// this extended EntityLog logs ALL persistent entities that are not in the unwantedMetaClasses List
// and also skips adding system properties like deleteTs and such

public class ExtEntityLog extends EntityLog {

    @Inject
    private Logger log;

    private List<String> systemProperties = Arrays.asList(
            "createdBy",
            "createTs",
            "deletedBy",
            "deleteTs",
            "updatedBy",
            "updateTs",
            "id",
            "version"
    );

    private List<String> unwantedMetaClasses = Arrays.asList(
            "ddcdi$ImportAttributeMapper",
            "ddcdi$ImportConfiguration",
            "ddcdi$ImportExecution",
            "ddcdi$ImportExecutionDetail",
            "ddcdi$UniqueConfiguration",
            "ddcdi$UniqueConfigurationAttribute"
    );

    public ExtEntityLog(Configuration configuration) {
        super(configuration);
    }

    protected void loadEntities() {
        super.loadEntities();

        List<MetaClass> metaClasses = getDesiredMetaClasses();

        metaClasses.forEach(mc -> {
            Set<String> props = getNonSystemProperties(mc);
            entitiesAuto.put(mc.getName(), props);
            entitiesManual.put(mc.getName(), props);
        });

        log.info("#### All desired entities added to log automatically ####");
    }

    private List<MetaClass> getDesiredMetaClasses() {
        return  metadataTools.getAllPersistentMetaClasses().stream().filter(mc -> !unwantedMetaClasses.contains(mc.getName())).collect(Collectors.toList());
    }

    private Set<String> getNonSystemProperties(MetaClass metaClass) {
        return metaClass.getProperties().stream().map(MetaProperty::getName).filter(mp -> !systemProperties.contains(mp)).collect(Collectors.toSet());
    }
}

Anyone using this would need to change the package and configure the unwantedMetaClasses array for their needs. You could also alter the systemProperties array if you want those logged.

2 Likes