Wicket NotSerializableException populating ListView - wicket

I'm working on a Web application which uses Spring and Hibernate frameworks. Right now I would like to remove the Serializable interface implementation from my Viaggio.java class, a JPA Entity.
Entities of this class are displayed inside my Wicket page through a PageableListView.
When I render the web page I get a NotSerializableException.
Here's a snippet of the code that instantiate the ListView:
final LoadableDetachableModel<List<Viaggio>> viaggiListModel = new LoadableDetachableModel<List<Viaggio>>() {
#Override
protected List<Viaggio> load() {
List<Viaggio> viaggi = utenteService.findAllViaggiForDataAndUsername(dataSelected, utente);
return viaggi;
}
};
final WebMarkupContainer container = new WebMarkupContainer("tableResult");
container.setOutputMarkupId(true);
PageableListView repeating = new PageableListView("repeating", viaggiListModel, 10) {
#Override
protected void populateItem(ListItem item) {
final Viaggio viaggio = (Viaggio) item.getModelObject();
String date = new SimpleDateFormat("dd-MM-yyyy").format(viaggio.getData());
item.add(new Label("data", date));
item.add(new Label("automezzo", new PropertyModel(viaggio, "targaMotrice")));
item.add(new Label("autista", new PropertyModel(viaggio, "primoAutista")));
item.add(new EnumLabel("status", new PropertyModel(viaggio, "status")));
......
I append down here the stacktrace printed:
24/02/2018 09:06:22,629 ERROR -JavaSerializer - Error serializing object class it.loginet.buonicarico.presentation.ViaggioListPage [object=[Page class = it.loginet.buonicarico.presentation.ViaggioListPage, id = 2, render count = 1]]
org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream$ObjectCheckException: The object type is not Serializable!
A problem occurred while checking object with type: it.loginet.buonicarico.domain.utente.Viaggio
Field hierarchy is:
2 [class=it.loginet.buonicarico.presentation.ViaggioListPage, path=2]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
private java.lang.Object org.apache.wicket.MarkupContainer.children[5] [class=org.apache.wicket.markup.html.WebMarkupContainer, path=2:tableResult]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
private java.lang.Object org.apache.wicket.MarkupContainer.children[0] [class=org.apache.wicket.markup.html.navigation.paging.PagingNavigator, path=2:tableResult:navigator]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
private java.lang.Object org.apache.wicket.MarkupContainer.children[0] [class=org.apache.wicket.markup.html.navigation.paging.PagingNavigation, path=2:tableResult:navigator:navigation]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.markup.html.list.LoopItem, path=2:tableResult:navigator:navigation:0]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.markup.html.navigation.paging.PagingNavigationLink, path=2:tableResult:navigator:navigation:0:pageLink]
protected final org.apache.wicket.markup.html.navigation.paging.IPageable org.apache.wicket.markup.html.navigation.paging.PagingNavigationLink.pageable [class=org.apache.wicket.markup.html.list.PageableListView, path=2:tableResult:repeating]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=org.apache.wicket.markup.html.list.ListItem, path=2:tableResult:repeating:0]
private java.lang.Object org.apache.wicket.MarkupContainer.children [class=[Ljava.lang.Object;]
java.lang.Object org.apache.wicket.Component.data[1] [class=org.apache.wicket.markup.html.basic.Label, path=2:tableResult:repeating:0:automezzo]
java.lang.Object org.apache.wicket.Component.data [class=org.apache.wicket.model.PropertyModel]
private java.lang.Object org.apache.wicket.model.ChainingModel.target [class=it.loginet.buonicarico.domain.utente.Viaggio] <----- field that is causing the problem
at org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream.internalCheck(CheckingObjectOutputStream.java:362)
at org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream.check(CheckingObjectOutputStream.java:341)
It clearly states my Viaggio field inside PropertyModel is not serializable, it finally points the finger to the "automezzo" Label i'm adding to the ListItem.
Where am I missing the point?
LoadableDetachableModel that wraps my list of Viaggio entities seems to be used correctly.

All your labels reference PropertyModels, which themselves directly reference your entity.
As a rule of thumb you should never call IModel#getObject() and keep its result around longer than needed:
final WebMarkupContainer container = new WebMarkupContainer("tableResult");
container.setOutputMarkupId(true);
PageableListView<Viaggio> repeating = new PageableListView<Viaggio>("repeating", viaggiListModel, 10) {
#Override
protected void populateItem(ListItem<Viaggio> item) {
final IModel<Viaggio> viaggio = item.getModel();
item.add(new Label("data", new AbstractReadOnlyModel<String>() {
public String getObject() {
return new SimpleDateFormat("dd-MM-yyyy").format(viaggio.getObject().getData())
}
});
item.add(new Label("automezzo", new PropertyModel(viaggio, "targaMotrice")));
item.add(new Label("autista", new PropertyModel(viaggio, "primoAutista")));
item.add(new EnumLabel("status", new PropertyModel(viaggio, "status")));
......
Instead of the AbstractReadOnlyModel you could use a PropertyModel too, and let a Converter do the formatting of the date.

Related

Dynamic injection using #SpringBean in wicket

I have a form that based on collected information generates a report. I have multiple sources from which to generate reports, but the form for them is the same. I tried to implement strategy pattern using an interface implementing report generator services, but that led to wicket complaining about serialization issues of various parts of the report generator. I would like to solve this without duplicating the code contained in the form, but I have not been able to find information on dynamic injection with #SpringBean.
Here is a rough mock up of what I have
public class ReportForm extends Panel {
private IReportGenerator reportGenerator;
public ReportForm(String id, IReportGenerator reportGenerator) {
super(id);
this.reportGenerator = reportGenerator;
final Form<Void> form = new Form<Void>("form");
this.add(form);
...
form.add(new AjaxButton("button1") {
private static final long serialVersionUID = 1L;
#Override
protected void onSubmit(AjaxRequestTarget target)
{
byte[] report = reportGenerator.getReport(...);
...
}
});
}
}
If I do it this way, wicket tries to serialize the concrete instance of reportGenerator. If I annotate the reportGenerator property with #SpringBean I receive Concrete bean could not be received from the application context for class: IReportGenerator
Edit: I have reworked implementations of IRerportGenerator to be able to annotate them with #Component and now I when I use #SpringBean annotation I get More than one bean of type [IReportGenerator] found, you have to specify the name of the bean (#SpringBean(name="foo")) or (#Named("foo") if using #javax.inject classes) in order to resolve this conflict. Which is exactly what I don't want to do.
I think the behavior you're trying to achieve can be done with a slight workaround, by introducing a Spring bean that holds all IReportGenerator instances:
#Component
public class ReportGeneratorHolder {
private final List<IReportGenerator> reportGenerators;
#Autowired
public ReportGeneratorHolder(List<IReportGenerator> reportGenerators) {
this.reportGenerators = reportGenerators;
}
public Optional<IReportGenerator> getReportGenerator(Class<? extends IReportGenerator> reportGeneratorClass) {
return reportGenerators.stream()
.filter(reportGeneratorClass::isAssignableFrom)
.findAny();
}
}
You can then inject this class into your Wicket page, and pass the desired class as a constructor-parameter. Depending on your Spring configuration you might need to introduce an interface for this as well.
public class ReportForm extends Panel {
#SpringBean
private ReportGeneratorHolder reportGeneratorHolder;
public ReportForm(String id, Class<? extends IReportGenerator> reportGeneratorClass) {
super(id);
IReportGenerator reportGenerator = reportGeneratorHolder
.getReportGenerator(reportGeneratorClass)
.orElseThrow(IllegalStateException::new);
// Form logic omitted for brevity
}
}
As far as I am able to find, looking through documentation and even the source for wicket #SpringBean annotation, this isn't possible. The closest I got is with explicitly creating a proxy for a Spring bean based on class passed. As described in 13.2.4 Using proxies from the wicket-spring project chapter in Wicket in Action.
public class ReportForm extends Panel {
private IReportGenerator reportGenerator;
private Class<? extends IReportGenerator> classType;
private static ISpringContextLocator CTX_LOCATOR = new ISpringContextLocator() {
public ApplicationContext getSpringContext() {
return ((MyApplication)MyApplication.get()).getApplicationContext();
}
};
public ReportForm(String id, Class<? extends IReportGenerator> classType) {
super(id);
this.classType = classType;
final Form<Void> form = new Form<Void>("form");
this.add(form);
...
form.add(new AjaxButton("button1") {
private static final long serialVersionUID = 1L;
#Override
protected void onSubmit(AjaxRequestTarget target)
{
byte[] report = getReportGenerator().getReport(...);
...
}
});
}
private <T> T createProxy(Class<T> classType) {
return (T) LazyInitProxyFactory.createProxy(classType, new
SpringBeanLocator(classType, CTX_LOCATOR));
}
private IReportGenerator getReportGenerator() {
if (reportGenerator = null) {
reportGenerator = createProxy(classType);
}
return reportGenerator;
}
}

How to get javax.validation payload validation for spring-cloud-aws in QueueMessageHandler working?

I'm writing some message consumer for an AWS SQS and want to validate the received message by using the javax.validation.constraints annotations.
Unfortunately I had to find out, that the used PayloadArgumentResolver of the spring-cloud-aws-messaging dependency uses a NoOpValidator.
So I tried to inject my own HandlerMethodArgumentResolver for the payload.
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory(
final ObjectMapper objectMapper, final Validator hibernateValidator) {
final MappingJackson2MessageConverter jacksonMessageConverter =
new MappingJackson2MessageConverter();
jacksonMessageConverter.setSerializedPayloadClass(String.class);
jacksonMessageConverter.setStrictContentTypeMatch(true);
jacksonMessageConverter.setObjectMapper(objectMapper);
final QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
argumentResolvers.add(new HeaderMethodArgumentResolver(null, null));
argumentResolvers.add(new HeadersMethodArgumentResolver());
argumentResolvers.add(new NotificationSubjectArgumentResolver());
argumentResolvers.add(new AcknowledgmentHandlerMethodArgumentResolver("Acknowledgment"));
argumentResolvers.add(new VisibilityHandlerMethodArgumentResolver("Visibility"));
final PayloadArgumentResolver payloadArgumentResolver =
new PayloadArgumentResolver(jacksonMessageConverter, hibernateValidator);
argumentResolvers.add(payloadArgumentResolver);
factory.setArgumentResolvers(argumentResolvers);
return factory;
}
So far so good and at first sight, it works well...
BUT as you can see I had to add the already in QueueMessageHandler existing argument resolvers as well to resolve the headers via #Headers/#Header of the message, too.
#SqsListener(
value = "queue",
deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void consume(
#Payload #Validated final QueueMessage queueMessage,
#Headers final Map<String,Object> headers) {
}
When I only add my PayloadArgumentResolver with the hibernate validator, it will be also used to resolve the headers, doh!
Is there any pretty solution for this or should I open an issue at spring-cloud-aws? I just want my payload to be validated via annotations :(
I don't think this is the best awswer but I have a working sample project that have this type of validation: https://github.com/Haple/sqslistener
#Data
#RequiredArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class EventDTO {
#NotNull(message = "foo is mandatory")
private final String foo;
#NotNull(message = "bar is mandatory")
private final String bar;
}
#Slf4j
#Service
#AllArgsConstructor
public class SampleListener {
#SqsListener("test_queue")
public void execute(final #Valid #Payload EventDTO event) {
log.info("OK: {}", event);
}
}
#Configuration
public class MessageHandler {
#Bean
QueueMessageHandler queueMessageHandler(final AmazonSQSAsync amazonSQSAsync,
final MessageConverter messageConverter,
final Validator validator) {
final QueueMessageHandlerFactory queueMessageHandlerFactory = new QueueMessageHandlerFactory();
final PayloadMethodArgumentResolver payloadMethodArgumentResolver = new PayloadMethodArgumentResolver(messageConverter, validator);
queueMessageHandlerFactory.setArgumentResolvers(Collections.singletonList(payloadMethodArgumentResolver));
queueMessageHandlerFactory.setAmazonSqs(amazonSQSAsync);
queueMessageHandlerFactory.setMessageConverters(Collections.singletonList(messageConverter));
return queueMessageHandlerFactory.createQueueMessageHandler();
}
}

How to pass additional data to GWT sub-editors?

i have this issue:
I have a PresenterWidget which contains sub-editors.
There are "container" elements which should be editable by this widget. These containers can be assigned to groups. To do so, i would like to fetch a list of all available groups from the server. So the widget is set up like this (i use GWTP):
public class ContainerEditorDialogPresenterWidget extends PresenterWidget<ContainerEditorDialogPresenterWidget.MyView> implements
ContainerEditorDialogUiHandlers {
private final PlaceManager placeManager;
private List<GroupDTO> groupList = new ArrayList<GroupDTO>();
private final DispatchAsync dispatcher;
#Inject
ContainerEditorDialogPresenterWidget(EventBus eventBus,
MyView view, PlaceManager placeManager, DispatchAsync dispatcher) {
super(eventBus, view);
getView().setUiHandlers(this);
this.dispatcher = dispatcher;
fetchGroups();
}
...
public void fetchGroups(){
FetchGroupsAction action = new FetchGroupsAction();
dispatcher.execute(action, new AsyncCallbackImpl<FetchGroupsResult>() {
#Override
public void onSuccess(FetchGroupsResult result) {
groupList = result.getGroupDtos();
eventBus.fireEvent(new GroupListUpdatedEvent(groupList));
}
});
}
So i call fetchGroups in the constructor to get it as early as possible. Since it is an AynchCallback, i get the result back "at some time". I then try to pass the values to the sub-editor with a GroupListUpdatedEvent. In there i have a Editor declared like this:
public class GroupListEditor extends Composite implements
IsEditor<ListEditor<String, GroupItemEditor>> {
private static StringListEditorUiBinder uiBinder = GWT
.create(StringListEditorUiBinder.class);
interface StringListEditorUiBinder extends
UiBinder<Widget, GroupListEditor> {
}
//Gives us access to the event bus.
#Inject private EventBus eventBus;
...
public GroupListEditor() {
initWidget(uiBinder.createAndBindUi(this));
eventBus.addHandler(GroupListUpdatedEvent.TYPE, new GroupListUpdatedEvent.GroupListUpdatedHandler() {
#Override
public void onGroupListUpdatedEvent(GroupListUpdatedEvent event) {
Log.debug("onContainerUpdatedEvent caught");
allGroups = event.getGroupList();
if(allGroups != null) {
for (GroupDTO g : allGroups) {
lbAllGroups.addItem(g.getName(), g.getId().toString());
}
lbAllGroups.setVisibleItemCount(5);
Log.debug("Item list = " + lbAllGroups.getItemCount());
} else {
Log.debug("GROUP LIST is Null!");
}
}
});
}
When i try to register the handler, i get an exception. So i expect the eventBus is not injected properly. What do i miss, how can i use events and the event bus if i am not in a Presenter?
And: Is this the right way at all to populate Editors with "utility" data? I guess Editor should be related directly to the data they care for. But how do i handle this kind of supplemental data?
Thanks :)
Do you use #UiField in your ContainerEditorDialogPresenterWidgetView for your GroupListEditor ?
If so then Dependency Injection won't work because you basically manually create the GroupListEditor which leads to EventBus being NULL.
I would also use Constructor Injection instead of field injection.
GroupListEditor:
#Inject
public GroupListEditor(EventBus eventBus) {
this.eventBus = eventBus;
}
ContainerEditorDialogPresenterWidgetView:
public class ContainerEditorDialogPresenterWidgetView {
#UiField(provided=true)
GroupListEditor groupListEditor;
#Inject
public ContainerEditorDialogPresenterWidgetView(GroupListEditor groupListEditor);
this.groupListEditor = groupListEditor;
initWidget();
}
}
Alternatively you could get an instance of your GroupListEditor via the Ginjector directly.

GWT editor frame work is not working

I have a bean named SignUpBean and it's editor is SignUpBeanEditor and following is its Driver interface.
public interface SignUpDriver extends SimpleBeanEditorDriver<SignUpBean, SignUpEditor>{
}
Following is entry point class
public class Signup implements EntryPoint {
private SignUpDriver signUpDriver;
private SignUpEditor signUpEditor;
private SignUpBean signUpBean;
private VerticalPanel verticalPanel;
private Label signUpLbl;
private Button submitButton;
private Button cancelButton;
private RequestBuilder requestBuilder;
final SignUpConverter signUpConverter=GWT.create(SignUpConverter.class);
public void onModuleLoad() {
signUpLbl = new Label("Sign Up");
signUpDriver = GWT.create(SignUpDriver.class);
signUpBean = new SignUpBean();
signUpEditor = new SignUpEditor();
submitButton = new Button("Submit");
cancelButton = new Button("Cancel");
signUpDriver.initialize(signUpEditor);
signUpDriver.edit(signUpBean);
System.out.println(signUpBean.getUserName());
submitButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
SignUpBean signUpBeanEdited=signUpDriver.flush();
}
}
}
}
I am getting only null value from signUpBeanEdited after giving value in UI. If i am initializing SignUpBean with constructor then also data is not binding to UI. My problem is I cant bind data in GWT UI using editor framework.
The fields( sub editors ) declared in SignUpEditor should be of at least DEFAULT scope. I guess you declared them as private. If so, the Editor Impl classes generated cannot access the fields to bind the data.
Changing scope to at least DEFAULT might solve your problem.

How to edit a Set<? extends EntityProxy> with GWT Editor framework?

for sake of simplicity:
public class Person
{
String name;
Set<Address> addresses;
}
public class Address
{
String city;
String street;
}
with and matching
public interface PersonProxy extends EntityProxy
{
public String getName();
public Set<AdressProxy> getAddresses();
}
and
public interface AdressProxy extends EntityProxy
{
public String getCity();
public String getStreet();
}
I got UiBuinder classes to edit AddressProxy
and it clear to me how to use ListEditor in case if I got List but data is Set in the Person class
how do I use Editor Framework to edit them?
Or may be how do I convert Set to List when it becomes PersonProxy?
I did an attempt to put a kind of adapter Editor class that would implement
LeafValueEditor<Set<AddressProxy>>
and then inside of the LeafValueEditor.setValue() move to a List and start a new driver.edit() on a separate Editor hierarchy that takes care of List editing but with now luck.
You should create a CompositeEditor<Set<AddressProxy>, AddressProxy, AddressEditor>, similar to a ListEditor but handling a Set instead of a List.
I suppose you could somehow delegate to a ListEditor though I'm really not sure.
I've done it with Points and Routes (one Route contains N Points):
Route (Composite):
#UiField
TextBox name;
#Ignore
#UiField
FlexTable listPoints;
PointsEditor pointsEditor = new PointsEditor();
....
pointsEditor.add(String id);
PointsEditor:
public class PointsEditor implements HasRequestContext<List<PointProxy>>, ValueAwareEditor<List<PointProxy>> {
List<PointProxy> points = new ArrayList<PointProxy>();
public void add(String id) {
PointProxy point = ctx.create(PointProxy.class);
point.setId(id);
points.add(point);
}
Route (server side):
#Embedded
private List<Point> points = new ArrayList<Point>();
RouteProxy
public interface RouteProxy extends EntityProxy {
abstract List<PointProxy> getPoints();
abstract void setPoints(List<PointProxy> points);
PointProxy
public interface PointProxy extends ValueProxy {
...
}