Hi,
i created an example for a problem i currently have. It’s a little bit about dynamic datasources and about related reference data - i’ll try to give you an explanation from the example application (which can be found here). Although i know that there are several questions around datasources and how to dynamically modify them, i wasn’t really able to get this case covered (or overlooked it):
It is a survey application where people can create surveys and other people can fill in an existing survey. The entity model looks like this:
The entities for the reference data are:
- Survey
- Question
- PossibleAnswer
The entities for the survey data are:
- FilledSurvey
- SelectedAnswer
Attached you’ll find an UML class diagram that shows the relationships (survey-entity-model.png).
What i want to achieve is the following: In the FilledSurvey Editor (filledSurvey-option-datasources.png) i want to list all questions that are related to the selected survey. For each Question i want to create an instance of a SelectedAnswer and list these instances in the table.
The first column of the table is the question. In the second column “answer” the user should be able to select one of the possibleAnswers for this question and the selected should be stored in the attribute answer of the SelectedAnswer entity.
I played with different options here for a while.
The first option was just to render a standard column for the answer attribute. This works but i was not able to restrict the select options only to this one that are related to the question in this row.
In the second implementation column “filteredAnswer” i created the column dynamically and used a nested-nested-nested datasource of the editor in order to display only the possible answers of the question of the current table row (see filled-survey-edit.xml as well):
<datasource id="filledSurveyDs"
class="com.company.survey.entity.survey.FilledSurvey"
view="filledSurvey-view">
<collectionDatasource id="answersDs"
property="answers">
<datasource id="questionsDs" property="question">
<collectionDatasource id="questionsPossibleAnswersDs" property="possibleAnswers" />
</datasource>
</collectionDatasource>
</datasource>
The Editor with the generated column looks like this:
public class FilledSurveyEdit extends AbstractEditor<FilledSurvey> {
// ...
@Inject
CollectionDatasource<PossibleAnswer, UUID> questionsPossibleAnswersDs
@Override
protected void initNewItem(FilledSurvey item) {
item.filledDate = new Date()
item.user = userSession.user
item.answers = []
survey.addValueChangeListener(new Component.ValueChangeListener() {
@Override
public void valueChanged(Component.ValueChangeEvent e) {
FilledSurvey filledSurvey = getItem()
Survey survey = (Survey) e.getValue();
survey.questions.each {
SelectedAnswer answer = metadata.create(SelectedAnswer.class)
answer.question = it
answer.filledSurvey = filledSurvey
filledSurvey.answers << answer
answersDs.addItem(answer)
}
}
});
super.initNewItem(item)
}
@Override
public void init(Map<String, Object> params) {
super.init(params);
answersTable.addGeneratedColumn("filteredAnswer", new Table.ColumnGenerator<SelectedAnswer>() {
@Override
Component generateCell(SelectedAnswer entity) {
LookupPickerField field = componentsFactory.createComponent(LookupPickerField.NAME);
// All PossibleAnswers should be displayed that belong to this Question (entity.question)
field.setDatasource(answersTable.getItemDatasource(entity), "answer");
field.setOptionsDatasource(questionsPossibleAnswersDs);
return field;
}
})
}
}
This approach works for the new case, where i create a new filled survey. The associations are correctly used of the nested datasource so that only the possible answers of current question in the table are selectable. This is quite amazing since i just tried to nest the datasource as much as i needed to get to the information. Although i don’t really understand what that exactly means and how this even works internally - it seems to work. But unfortunately only for the new case. When i edit a filled survey and change one of the answers i get the following Exception: org.eclipse.persistence.exceptions.ValidationException
2016-09-01 07:59:17.431 ERROR [http-nio-8080-exec-5/app/admin] com.haulmont.cuba.web.log.AppLog - Exception in com.haulmont.cuba.web.toolkit.ui.CubaButton:
com.vaadin.server.ServerRpcManager$RpcInvocationException: Unable to invoke method click in com.vaadin.shared.ui.button.ButtonServerRpc
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:160)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:118)
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:79)
at com.haulmont.cuba.web.sys.CubaVaadinServletService$CubaUidlRequestHandler.lambda$synchronizedHandleRequest$92(CubaVaadinServletService.java:307)
at com.haulmont.cuba.web.sys.CubaVaadinServletService.withUserSession(CubaVaadinServletService.java:189)
at com.haulmont.cuba.web.sys.CubaVaadinServletService$CubaUidlRequestHandler.synchronizedHandleRequest(CubaVaadinServletService.java:307)
at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1409)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:369)
at com.haulmont.cuba.web.sys.CubaApplicationServlet.serviceAppRequest(CubaApplicationServlet.java:254)
at com.haulmont.cuba.web.sys.CubaApplicationServlet.service(CubaApplicationServlet.java:163)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
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:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1520)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1476)
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)
Caused by: java.lang.reflect.InvocationTargetException
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:497)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:158)
... 38 more
Caused by: com.vaadin.event.ListenerMethod$MethodException: Invocation of method buttonClick in com.haulmont.cuba.web.gui.components.WebButton$1 failed.
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:528)
at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:198)
at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:161)
at com.vaadin.server.AbstractClientConnector.fireEvent(AbstractClientConnector.java:1030)
at com.vaadin.ui.Button.fireClick(Button.java:377)
at com.haulmont.cuba.web.toolkit.ui.CubaButton.fireClick(CubaButton.java:54)
at com.vaadin.ui.Button$1.click(Button.java:54)
... 43 more
Caused by: com.vaadin.ui.Table$CacheUpdateException: Error during Table cache update.
at com.vaadin.ui.Table.maybeThrowCacheUpdateExceptions(Table.java:1800)
at com.vaadin.ui.Table.refreshRenderedCells(Table.java:1789)
at com.vaadin.ui.Table.refreshRowCache(Table.java:2729)
at com.vaadin.ui.Table.containerItemSetChange(Table.java:4666)
at com.haulmont.cuba.web.gui.data.CollectionDsWrapper.fireItemSetChanged(CollectionDsWrapper.java:112)
at com.haulmont.cuba.web.gui.data.CollectionDsWrapper$ContainerDatasourceCollectionChangeListener.collectionChanged(CollectionDsWrapper.java:263)
at com.haulmont.cuba.gui.data.impl.WeakCollectionChangeListener.collectionChanged(WeakCollectionChangeListener.java:41)
at com.haulmont.cuba.gui.data.impl.CollectionPropertyDatasourceImpl.fireCollectionChanged(CollectionPropertyDatasourceImpl.java:731)
at com.haulmont.cuba.gui.data.impl.CollectionPropertyDatasourceImpl.committed(CollectionPropertyDatasourceImpl.java:703)
at com.haulmont.cuba.gui.data.impl.DsContextImpl.notifyAllDsCommited(DsContextImpl.java:222)
at com.haulmont.cuba.gui.data.impl.DsContextImpl.commit(DsContextImpl.java:168)
at com.haulmont.cuba.gui.components.EditorWindowDelegate.commit(EditorWindowDelegate.java:255)
at com.haulmont.cuba.web.gui.WebWindow$Editor.commitAndClose(WebWindow.java:1437)
at com.haulmont.cuba.gui.components.AbstractEditor.commitAndClose(AbstractEditor.java:110)
at com.haulmont.cuba.gui.components.EditorWindowDelegate$2.actionPerform(EditorWindowDelegate.java:93)
at com.haulmont.cuba.web.gui.components.WebButton.performAction(WebButton.java:48)
at com.haulmont.cuba.web.gui.components.WebButton$1.buttonClick(WebButton.java:39)
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:497)
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:508)
... 49 more
Caused by: Exception [EclipseLink-7242] (Eclipse Persistence Services - 2.6.2.cuba6): org.eclipse.persistence.exceptions.ValidationException
Exception Description: An attempt was made to traverse a relationship using indirection that had a null Session. This often occurs when an entity with an uninstantiated LAZY relationship is serialized and that relationship is traversed after serialization. To avoid this issue, instantiate the LAZY relationship prior to serialization.
at org.eclipse.persistence.exceptions.ValidationException.instantiatingValueholderWithNullSession(ValidationException.java:1024)
at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiate(UnitOfWorkValueHolder.java:233)
at org.eclipse.persistence.internal.indirection.DatabaseValueHolder.getValue(DatabaseValueHolder.java:101)
at org.eclipse.persistence.indirection.IndirectSet.buildDelegate(IndirectSet.java:218)
at org.eclipse.persistence.indirection.IndirectSet.getDelegate(IndirectSet.java:388)
at org.eclipse.persistence.indirection.IndirectSet$1.(IndirectSet.java:461)
at org.eclipse.persistence.indirection.IndirectSet.iterator(IndirectSet.java:460)
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:497)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1210)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1019)
at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:905)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:896)
at org.codehaus.groovy.runtime.InvokerHelper.asIterator(InvokerHelper.java:571)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getPropertySpreadSafe(ScriptBytecodeAdapter.java:471)
at com.company.survey.web.survey.filledsurvey.FilledSurveyEdit$2.generateCell(FilledSurveyEdit.groovy:95)
at com.company.survey.web.survey.filledsurvey.FilledSurveyEdit$2.generateCell(FilledSurveyEdit.groovy)
at com.haulmont.cuba.web.gui.components.WebAbstractTable$4.generateCell(WebAbstractTable.java:1439)
at com.vaadin.ui.Table.parseItemIdToCells(Table.java:2402)
at com.vaadin.ui.Table.getVisibleCellsNoCache(Table.java:2275)
at com.vaadin.ui.Table.refreshRenderedCells(Table.java:1778)
... 69 more
Here you’ll find the views.xml (in case something is wrong with this).
I don’t really get what the exception wants to tell me. Additionally in the UI the following error occurs: exception-with-missing-picker-field.png - which indicates the error on the UI but also shows that in the background the picker field that i used to change the value could not be painted on the screen anymore. The error occurs when i select “Save” in the editor.
So here’s my question (after this fairly long explanation):
Do you either know what i have to change in the first approach to just show me the possible answers for this question, or do you know what i have to change in the second approach in order to make the error disappear?
Perhaps you might be able to give a rough explenation how this nested-nested-nested datasource beast works under the hood - because right now this is a fair amount of magic going on…
Bye,
Mario