Applying filter to datasource in controller

Hi,
Is there a way to add filters to datasources from the controller code, without adding it to view?
Found the

setQueryFilter(QueryFilter filter)

method but I couldn’t figure out how to build the filter itself. Couldn’t find any documentation regarding programmatic filtering as well.

  1. Simple condition, date condition
  2. Multiple conditions (AND, OR, nesting)
    Thanks!
2 Likes

I have found this but still trying to find out how to use the method…

Hi Oleg,

You can create an instance of QueryFilter and then pass it to the CollectionDatasource.setQueryFilter() method to modify the query and then call CollectionDatasource.refresh(). The QueryFilter can be created from XML (see an example here) or from a structure of LogicalCondition and Clause objects.

The same mechanism is used when you define the filter inside the query element of the controller XML. It is explained in the docs.

Hi Konstantin,
Thanks for feedback.
Found the examples for creating the

QueryFilter

from XML in git repository, but unfortunately this is not a viable option.
On the other hand after spending quite a while on this issue, could not figure out how to actually build

QueryFilter

from

LogicalCondition

and

Clause

structure.
Is there a ready example you can share or doc how to do that?

Here it is: https://github.com/cuba-labs/query-filter

The main part:


LogicalCondition orCondition = new LogicalCondition("", LogicalOp.OR);
orCondition.getConditions().add(new Clause("", "e.name like :(?i)custom$paramValue", null, null, null));
orCondition.getConditions().add(new Clause("", "e.email like :(?i)custom$paramValue", null, null, null));
QueryFilter queryFilter = new QueryFilter(orCondition);
customersDs.setQueryFilter(queryFilter);
customersDs.refresh(ParamsMap.of("paramValue", "%" + value + "%"));

The conditions API is not very convenient for ad-hoc usage because it was designed for generic Filter component. Could you explain in more detail why you need such programmatic filtering instead of declarative? We would like to have some motivation to improve the API.

1 Like

Konstantin, thanks a lot for support.

My main objective is (similarly to what you’ve provided in example) to apply a quick filter by adding a simple component to ButtonsPanel (in my case a checkbox). Using the generic filter is not desirable as the filtered table is a nested one, filter parameters are very basic and full-blown filter would be an overkill.

After further research and “inspiration” from sample-timesheets app I reckon declarative approach might be a viable option. The only issue I have with it is that by default CUBA doesn’t create queries for datasources & I would prefer not meddling with it in the view file, but rather sort out the filter in controller. If I understand correctly declarative approach requires building the query & the filter with parameters in view file (and adding

<filter>

only without

<query>

to view would not work).
In your example though the query for Customer was included nonetheless.

That said, somehow, I was unable to implement your solution in my project correctly and would appreciate if you could help me resolve the error.

I have a many-to-many relationship Customer - CustomerStrategy - Strategy*. In customer-edit screen I would like to filter the strategies that are valid on any given date (for simplicity current date activated by checkbox) using *validFrom and validTo properties in Strategy.

My

dsContext

in

Customer-edit.xml

:


    <dsContext>
        <datasource id="customerDs"
                    class="com.company.segapp.entity.Customer"
                    view="customer-view-default">
            <query><![CDATA[select c from segapp$Customer c]]></query>
            <collectionDatasource id="strategiesDs"
                                  property="strategies"/>
            <query>
                <![CDATA[select cs from segapp$CustomerStrategy cs where cs.customer = :ds$customerDs]]>
            </query>
        </datasource>
    </dsContext>

My

CustomerEdit

controller:


         activeOnlyCheckbox.addValueChangeListener(event -> {


            if (Boolean.TRUE.equals(event.getValue())) {

                Date today = new Date();

                LogicalCondition andCondition = new LogicalCondition("", LogicalOp.AND);
                andCondition.getConditions().add(new Clause("", "cs.strategy.validFrom <= :(?i)custom$today", null, null, null));
                andCondition.getConditions().add(new Clause("", "cs.strategy.validTo >= :(?i)custom$today", null, null, null));
                QueryFilter queryFilter = new QueryFilter(andCondition);

                strategiesDs.setQueryFilter(queryFilter);
                strategiesDs.refresh(ParamsMap.of("today", today));

            } else {

                strategiesDs.setQueryFilter(null);
                strategiesDs.refresh();

            }
        });

As a result I get a UnsupportedOperationException:


java.lang.UnsupportedOperationException
	at com.haulmont.cuba.gui.data.impl.CollectionPropertyDatasourceImpl.setQueryFilter(CollectionPropertyDatasourceImpl.java:678)
	at com.company.segapp.web.customer.CustomerEdit.lambda$init$0(CustomerEdit.java:44)
	at com.haulmont.bali.events.EventRouter.fireEvent(EventRouter.java:45)
	at com.haulmont.cuba.web.gui.components.WebAbstractField.lambda$attachListener$6f56ebaa$1(WebAbstractField.java:278)
	at sun.reflect.GeneratedMethodAccessor126.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:510)
	at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:200)
	at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:163)
	at com.vaadin.server.AbstractClientConnector.fireEvent(AbstractClientConnector.java:1037)
	at com.vaadin.ui.AbstractField.fireValueChange(AbstractField.java:1181)
	at com.vaadin.ui.AbstractField.setValue(AbstractField.java:580)
	at com.vaadin.ui.AbstractField.setValue(AbstractField.java:474)
	at com.vaadin.ui.AbstractField.setValue(AbstractField.java:454)
	at com.vaadin.ui.CheckBox$1.setChecked(CheckBox.java:63)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:158)
	at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:119)
	at com.vaadin.server.communication.ServerRpcHandler.handleInvocation(ServerRpcHandler.java:442)
	at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:414)
	at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:274)
	at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:90)
	at com.haulmont.cuba.web.sys.CubaVaadinServletService$CubaUidlRequestHandler.lambda$synchronizedHandleRequest$0(CubaVaadinServletService.java:314)
	at com.haulmont.cuba.web.sys.CubaVaadinServletService.withUserSession(CubaVaadinServletService.java:196)
	at com.haulmont.cuba.web.sys.CubaVaadinServletService$CubaUidlRequestHandler.synchronizedHandleRequest(CubaVaadinServletService.java:314)
	at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
	at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1422)
	at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:384)
	at com.haulmont.cuba.web.sys.CubaApplicationServlet.serviceAppRequest(CubaApplicationServlet.java:276)
	at com.haulmont.cuba.web.sys.CubaApplicationServlet.service(CubaApplicationServlet.java:185)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at com.haulmont.cuba.web.sys.CubaHttpFilter.handleNotFiltered(CubaHttpFilter.java:108)
	at com.haulmont.cuba.web.sys.CubaHttpFilter.doFilter(CubaHttpFilter.java:95)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:789)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1437)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)

Oleg,

You cannot use neither query nor QueryFilter in a nested datasource. A nested datasource doesn’t load data itself, it just serves as a wrapper around a collection attribute of a master entity.

In order to use filtering, you can create a standalone collection datasource for CustomerStrategy and link its query to the Customer datasource using a parameter as explained here. There is another restriction though - you cannot use composition with a standalone datasource, which means your CustomerStrategy objects will be saved independently from the owning Customer. Let me know if you still need composition in this case, I’ll try to provide a solution for filtering of a nested datasource - however it won’t be straightforward.

Dear Konstantin,

Thanks again. This time it works as expected. At this moment I’m not using composition as CustomerStrategy will also have additional fields and therefore association works better.

One side-note with standalone collection datasource though : when creating a new CustomerStrategy, Customer field is not populated automatically, and Customer in question should be passed as a parameter.

Here’s sample code (in case anyone might be interested, picked up from “screen manipulation sample”):
New action in CustomerEdit controller to pass Customer as a parameter


    @Inject
    private Metadata metadata;

    @Inject
    private Datasource<Customer> customerDs;

    public void onAdd(Component source) {

        CustomerStrategy customerStrategy = metadata.create(CustomerStrategy.class);
        CustomerStrategyEdit dialog = (CustomerStrategyEdit) openEditor("segapp$CustomerStrategy.edit", customerStrategy, WindowManager.OpenType.DIALOG, ParamsMap.of("customer", customerDs.getItem()));

        dialog.addCloseWithCommitListener(()->{
            strategiesDs.refresh();
        });

    }

It works, just not sure if

customerDs.getItem()

is the most appropriate way to get currently selected Customer.

Picking up parameter in CustomerStrategyEdit controller and assigning it to newly created object


    @WindowParam
    private Customer customer;

    @Override
    protected void initNewItem(CustomerStrategy item) {
        if(customer!=null) {
            item.setCustomer(customer);
        }
    }

If it’s the Customer edit screen, just getItem() will also work - it returns the currently edited entity.

Also, in the standard CreateAction there is a special method for setting initial values in created entity, for example:


    @Override
    protected void postInit() {
        myCreateAction.setInitialValues(ParamsMap.of("customer", getItem()));
    }

Noted with thanks.
Appreciate your support!

Great, Query Filter is a really cool thing I was unaware before.

Hello. I have the same problem, but in my case nested datasource must be with composition. How can I create filter in this case?

Hi,
composition is just a specific type of association, so there is nothing special about it in that respect. Netsed datasources cannot be filtered by their nature - they are just wrappers. You have to use a standalone datasource if you need to apply a filter (see earlier in this topic).

Thank you Ilya. I understand differences nested and standalone DS. But may be you know workaround, what fixed my problem?

Sorry, I didn’t get what exactrly your problem is.

I need filter for nested datasource… or anything else like as filter… may be programatically search from DS…

As this topic suggests, the common WA is to use a separate DS. Any reasons this doesn’t work for you?

But how said Konstantin I will have another problem because it will be not nested DS and will be problem with commit. Ok, I understand, thanks.