Fetch the attribute, good boi (actually not a good boy)

There’s something I’m not quite getting when it comes to fetching attributes in my view. I have an edit screen that works perfectly when it comes to populating the UI components and performing CRUD operations, but it breaks when adding entities in the controller code. Let me give you the background:

I have a “OperationParameter” entity that has a one-to-one composition with a “Parameter” entity. In turn the “Parameter” entity has a one-to-many composition with “ParameterValue” entities. Each “ParameterValue” has a one-to-one association with a “ParameterItem” entity, and lastly the “ParameterItem” has a one-to-many composition with “ParameterList” entities.

Phew, sounds complicated to explain but at the end of the day it’s just a hierarchy. My OperationParameter edit screen has the following data section containing the in-line view:

<data>
    <instance id="operationParameterDc"
              class="com.gektron.pp.entity.route.OperationParameter">
        <view extends="_local">
            <property name="parameter" view="_local">
                <property name="type" view="_minimal"/>
                <property name="unit" view="_minimal"/>
                <property name="values" view="_local">
                    <property name="item" view="_local">
                        <property name="list" view="_local"/>
                    </property>
                </property>
            </property>
        </view>
        <loader/>
        <instance id="parameterDc" property="parameter">
            <collection id="parameterValuesDc" property="values"/>
        </instance>
    </instance>
</data>

As you can see, I’ve made sure that the entire hierarchy is traversed as I do need to fetch data from each level all the way down to the ParameterList. For example, starting at the bottom, the property name “list” refers to the attribute in the ParameterItem, which in turn is referred to by the “item” attribute in the PropertyValue, etc.

As I mentioned this works perfectly for all UI logic and the built-in CRUD logic, nice one CUBA!

However, things start to go wrong when I try to add some ParameterValue entities in my code. I have a function called addValues() shown below:

private void addValues(ParameterType parameterType) {

    parameterValuesDc.getItems().forEach(i -> dataContext.remove(i));
    parameterValuesDc.getMutableItems().clear();
    Parameter parameter = parameterDc.getItem();

    if (parameterType != null) {
        parameterType.getItems().stream().map(ParameterLink::getItem)
                .sorted(Comparator.comparing(ParameterItem::getSort))
                .forEach(i -> {
            ParameterValue parameterValue = metadata.create(ParameterValue.class);
            parameterValue.setItem(i);
            parameterValue.setNumber(0.0);
            parameterValue.setText("");
            parameterValue.setParameter(parameter);
            parameterValuesDc.getMutableItems().add(parameterValue);
            //i.getList();
        });
    }
}

In a nutshell, this function deletes the ParameterValue entities currently associated with the Parameter, and then adds new ParameterValue entities based on the ParameterType passed to the function. This code executes without error and updates the UI correctly, all seems good so far, this developer deserves a pat on the back, but wait…

I need to write another part of this function to dig down a bit further into the ParameterItem and ParameterList entities. If I uncomment the last line containing “i.getList()” I get the following error:

IllegalStateException: Cannot get unfetched attribute [list] from detached object com.gektron.pp.entity.parameter.ParameterItem-036cb281-8c4d-d218-698a-45c3ea9ef1af [detached].

I know this is probably simple but I have spent 2 days pouring over the documentation and trying to find any similar scenarios but to no avail. The only clue I’ve got seems to be that different ParameterType entities will have items with different lists (ParameterList), which when referenced by the ParameterValue item will cause the above error because they possibly weren’t fetched when the view was first created.

I have tried various methods of reloading data but nothing works, I continue to get this error. Please help me CUBA, you’re my only hope.

The code doesn’t show wherethe methods argument parameterType is coming from so difficult to see.

what I can see that might help you is that you define a nice view for the operationParameterDc container but there are no views for the parameterDc and it’s nested parameterValuesDc containers.

So my first guess would be that you forgot to define a view there and this is the cause?

If not, I think it would be good to create a minimal project reproducing this issue and share it. Anyone using Cuba seems to struggle a bit with the view concept (so did I) at first.

Hi Tom,

Thank you for your reply. However it is my understanding that you can’t define views for nested data containers (instances or collections). In fact CUBA Studio does not seem to offer any method of doing this.

I have much less knowledge than you though, so if this is the solution to the original problem could you elaborate slightly on how this would be done?

Yay, after pouring over the documentation some more, I’ve found the solution and it’s pretty obvious really. Here’s my new code that fixes it:

private void addValues(ParameterType parameterType) {

    parameterValuesDc.getItems().forEach(i -> dataContext.remove(i));
    parameterValuesDc.getMutableItems().clear();
    Parameter parameter = parameterDc.getItem();

    if (parameterType != null) {
        parameterType.getItems().stream().map(ParameterLink::getItem)
                .sorted(Comparator.comparing(ParameterItem::getSort))
                .forEach(i -> {
            ParameterItem item = dataManager.reload(i, operationParameterDc.getView()
                    .getProperty("parameter").getView()
                    .getProperty("values").getView()
                    .getProperty("item").getView());
            ParameterValue parameterValue = metadata.create(ParameterValue.class);
            parameterValue.setItem(item);
            parameterValue.setNumber(0.0);
            parameterValue.setText("");
            parameterValue.setParameter(parameter);
            parameterValuesDc.getMutableItems().add(parameterValue);
        });
    }
}

The key was not to set i as the ParameterValue item as I did before, but rather to reload i into its inline view as follows:

ParameterItem item = dataManager.reload(i, operationParameterDc.getView()
        .getProperty("parameter").getView()
        .getProperty("values").getView()
        .getProperty("item").getView());

I then use the returned item to set the ParameterValue item. Bingo the list attribute has been fetched for the item and the error goes away.

Hope this helps someone else.

Hi Steve,
Sorry I didn’t notice they were nested. thought paramterDc was top level.

Thanks for posting the solution.
But what is the purpose of .getProperty("...").getView() calls? In my understanding, the container’s view operationParameterDc.getView() is all that you need.

Hi Konstantin,

Thank you for your comment. The reference to the ParameterItem entity from the top level OperationParameter entity in my schema is a bit convoluted, but necessary in the grand scheme of how I need the data to be related in the actual database.

As you can see from my screen layout for the OperationParameter entity, I have multiple nested views:

<data>
    <instance id="operationParameterDc"
              class="com.gektron.pp.entity.route.OperationParameter">
        <view extends="_local">
            <property name="parameter" view="_local">
                <property name="type" view="_minimal"/>
                <property name="unit" view="_minimal"/>
                <property name="values" view="_local">
                    <property name="item" view="_local">
                        <property name="list" view="_local"/>
                    </property>
                </property>
            </property>
        </view>
        <loader/>
        <instance id="parameterDc" property="parameter">
            <collection id="parameterValuesDc" property="values"/>
        </instance>
    </instance>
</data>

If I try the code just using operationParameterDc.getView() call without the getProperty().getView() traversal calls, then I get the following exception:

QueryException: 
Exception Description: The value e.parameter supplied to the query hint eclipselink.left-join-fetch navigated a non-existent relationship.  The relationship e.parameter does not exist.
Query: ReadObjectQuery(referenceClass=ParameterItem jpql="select e from pp_ParameterItem e where e.id = :entityId")
FetchGroup(){deleteTs, parameter => {class java.lang.Object=FetchGroup(parameter){deleteTs, unit => {class java.lang.Object=FetchGroup(unit){deleteTs, name, deletedBy}}, precision, values => {class java.lang.Object=FetchGroup(values){deleteTs, number, item => {class java.lang.Object=FetchGroup(item){deleteTs, name, sort, list => {class java.lang.Object=FetchGroup(list){deleteTs, parameterItem => {class java.lang.Object=FetchGroup(parameterItem){name, sort, type}}, name, sort, deletedBy}}, type, deletedBy}}, parameter => {class java.lang.Object=FetchGroup(parameter){precision, name, sort}}, text, deletedBy}}, name, sort, type => {class java.lang.Object=FetchGroup(type){deleteTs, name, deletedBy}}, deletedBy}}, deletedBy}

.
The only way it works is to reload the ParameterItem entity into its corresponding view using:

ParameterItem item = dataManager.reload(i, operationParameterDc.getView()
        .getProperty("parameter").getView()
        .getProperty("values").getView()
        .getProperty("item").getView());

I hope this clarifies my thinking. However I certainly welcome why you suggest reloading an entity into a parent view should be the way to do it.

Many thanks as always.

Sorry, I overlooked that you reload the entity which is lower in the graph, so you need to get its view accordingly.