We don’t need no MxModelReflection

The Mx Model Reflection module is a technical component from the Mendix App Store that is used in almost every Mendix project. The main reason for this is that other well-known modules from the app store make use of MxModelReflection, such as the ExcelImporter, ExcelExporter, FixedLengthExporter, and DBReplication modules.

It is a very useful component, but what does the MxModelReflection module actually do?

  • It allows certain users of the Mendix application to see the module names used in the model.
  • Users can select for which modules they want to load the entity information and microflows.
  • It adds tables to your database that will save this information.
  • By using the objects that represent other entities, there is a meta-model at run-time.
  • The meta-model can be used to configure exports and imports of data at run-time, like with the ExcelImporter.

This is very handy! But there are some potential weaknesses:

  • When adding this module, you also will add a number of tables to your application database, which is not very elegant. Also, companies may have architectural policy to mimimize the amount of changes to the database for applications that are already in production. So adding new tables that don’t contain business data is sometimes undesired.
  • It poses a security risk by allowing some users to see (parts of the) source model of the application. This information could give attackers insight in weaknesses that your application might contain.
  • You need to sync the model reflection when you changed something in your model. In every environment (DTAP)… If you forget this on any environment, it can break important functionality, e.g. the ExcelImporter will throw ugly error messages at the user if the model reflection is not synced properly.

In this post I will show a way to get meta-information of your application without using the MxModelReflection module. This will give developers more flexibility and more control over the information shown in the user interface. Also it doesn’t add any tables to the database!

I will create the following Java actions to get the information we need.

java actions

I use the following domain model. Note that the entities are non-persistent, so no database tables will be created:

 

The first Java Action GetAllEntityNames takes no parameters, and returns a list of Entity objects.

public class GetAllEntityNames extends UserAction<java.util.List<IMendixObject>>
{
	public GetAllEntityNames()
	{
		super();
	}

	@Override
	public java.util.List<IMendixObject> executeAction() throws Exception
	{
		// BEGIN USER CODE
		ArrayList<IMendixObject> result = new ArrayList<IMendixObject>(); 
		for(IMetaObject metaObject : Core.getMetaObjects())
		{
			IMendixObject entity = Core.instantiate(getContext(), Entity.getType());
			entity.setValue(getContext(), Entity.MemberNames.CompleteName.toString(), metaObject.getName());
			result.add(entity);
		}
		return result;
		// END USER CODE
	}

Note the use of Core.getMetaObjects(). This part of the Mendix Java API will result in a list of all the entities in the application model. Each entity is represented by an object of the IMetaObject interface. See the Mendix Apidocs for more info.

Use this Java action in a datasource microflow for selecting a single entity, like this:

Microflow DS_AllEntityNames
Microflow DS_AllEntityNames

Of course, you can customize this all you want: maybe you only want to have the entity names of a certain module: then you could just filter the list in the microflow (or change the Java code).

Now, if the user has selected an entity, you might want go one level deeper: the members. Members of an entity consist of primitive members (attributes) and assocations (references and reference sets). To get the associations that a certain entity has, I created this Java action GetAssociationsOfEntity, which takes 1 string parameter (entityName), and returns a list of Assocation objects:

public class GetAssociationsOfEntity extends UserAction<java.util.List<IMendixObject>>
{
	private String entityName;

	public GetAssociationsOfEntity(String entityName)
	{
		super();
		this.entityName = entityName;
	}

	@Override
	public java.util.List<IMendixObject> executeAction() throws Exception
	{
		// BEGIN USER CODE
		IMetaObject metaObject = Core.getMetaObject(entityName);
		if (metaObject==null) return null;
		ArrayList<IMendixObject> result = new ArrayList<IMendixObject>();
		for(IMetaAssociation metaAssoc : metaObject.getMetaAssociationsParent())
		{
			IMendixObject assoc = Core.instantiate(getContext(), Association.getType());
			assoc.setValue(getContext(), Association.MemberNames.CompleteName.toString(), metaAssoc.getName());
			assoc.setValue(getContext(), Association.MemberNames.TargetEntityName.toString(), metaAssoc.getChild().getName());
			result.add(assoc);
		}
		return result;
		// END USER CODE

Note the call to Core.getMetaObject(entityName) to get the IMetaObject for the given entity. By calling getMetaAssociationsParent() on the IMetaObject, we get a list of assocations owned by that entity.

Get the members is very similar to the associations. Calling getMetaPrimitives() on the IMetaObject will get a list of its attributes. This Java action GetAttributesOfEntity will take 1 string parameter (entityName), and will return a list of Attribute objects:

public class GetAttributesOfEntity extends UserAction<java.util.List<IMendixObject>>
{
	private String entityName;

	public GetAttributesOfEntity(String entityName)
	{
		super();
		this.entityName = entityName;
	}

	@Override
	public java.util.List<IMendixObject> executeAction() throws Exception
	{
		// BEGIN USER CODE
		IMetaObject metaObject = Core.getMetaObject(entityName);
		if (metaObject==null) return null;
		ArrayList<IMendixObject> result = new ArrayList<IMendixObject>();
		for(IMetaPrimitive metaPrim : metaObject.getMetaPrimitives())
		{
			IMendixObject attribute = Core.instantiate(getContext(), Attribute.getType());
			attribute.setValue(getContext(), Attribute.MemberNames.AttributeName.toString(), metaPrim.getName());
			attribute.setValue(getContext(), Attribute.MemberNames.AttributeType.toString(), metaPrim.getType().toString());
			result.add(attribute);
		}
		return result;
		// END USER CODE

By using these Java actions, you can build your own re-usable modules without having to depend on MxModelReflection!

If you have any questions, remarks or suggestions, feel free to contact me or leave a reply below.

6 responses to “We don’t need no MxModelReflection”

  1. Rolf Avatar
    Rolf

    Bart,

    Is er ook een manier om middels een java action de actuele modulenaam en microflownaam uit de context te halen?

    Met vriendelijke groet,

    Rolf

    1. Bert Avatar
      Bert

      Ja dat zou handig zijn!

    2. Bart Groot Avatar
      Bart Groot

      Your’e asking if it’s possible to get the name of the current module+microflow with a Java action.
      I haven’t tried it, but looking at the Apidocs, it should be possible by calling:
      Stack actionStack = this.getContext().getActionStack();

      The you can look at the top of the stack to get the last calling microflow as a CoreAction object. You can get the name of that microflow by calling getActionName() (see https://apidocs.mendix.com/4/runtime/classcom_1_1mendix_1_1core_1_1actionmanagement_1_1_core_action_3_01_r_01_4.html )

  2. Bert Koot Avatar
    Bert Koot

    Hi Bart,

    I missed the update. I found the function getActionName() but the Stack seems to be empty. What is wrong with my Java code:

    public class GetMicroflowName extends CustomJavaAction
    {
    public GetMicroflowName(IContext context)
    {
    super(context);
    }

    @Override
    public String executeAction() throws Exception
    {
    // BEGIN USER CODE
    Stack<CoreAction> actionStack = this.getContext().getActionStack();
    String naamMF ;

    if (actionStack.isEmpty()) {
    CoreAction laatsteAction = actionStack.peek();

    naamMF = laatsteAction.getActionName();
    } else {
    naamMF = “Unknown.”;
    }

    return naamMF ;

    // END USER CODE
    }

    /**
    * Returns a string representation of this action
    */
    @Override
    public String toString()
    {
    return “GetMicroflowName”;
    }

    }

    1. Bart Groot Avatar
      Bart Groot

      Shouldn’t it be “if (!actionStack.isEmpty()) actionStack.peek()”? (note the ! )

  3. Marcus Groen Avatar

    You can use this in Mendix 7 with your own Microflow entity:

    public IMendixObject executeAction() throws Exception
    {
    // BEGIN USER CODE
    Stack<CoreAction> actionStack = getContext().getActionStack();
    if (!actionStack.isEmpty()) {
    CoreAction lastAction = actionStack.get(actionStack.size()-2);
    IMendixObject entity = Core.instantiate(getContext(), Microflow.getType());
    entity.setValue(getContext(), Microflow.MemberNames.CompleteName.toString(), lastAction.getActionName());
    return entity;
    } else {
    return null;
    }
    // END USER CODE
    }

    And use this for listing all microflows:

    public java.util.List executeAction() throws Exception
    {
    // BEGIN USER CODE
    ArrayList result = new ArrayList();
    TreeSet mfSet = new TreeSet(Core.getMicroflowNames());
    for(String mfName : mfSet)
    {
    IMendixObject entity = Core.instantiate(getContext(), Microflow.getType());
    entity.setValue(getContext(), Microflow.MemberNames.CompleteName.toString(), mfName);
    result.add(entity);
    }
    return result;
    // END USER CODE
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.