tapestry5.3.7 t:grid not working with xmlgregoriancalendar - grid

Imported ejb entity into tapestry project looks like this
its in "generated sorces (jax-ws)"
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.datatype.XMLGregorianCalendar;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "money", propOrder = {
"moneyAmount",
"moneyCurrency",
"moneyDate",
"moneyFinishDate",
"moneyIO",
"moneyId",
"moneyName",
"moneyNextDate",
"moneyNumber",
"moneyPurpose",
"moneyRenewal",
"moneyTs"
})
public class Money {
protected Double moneyAmount;
protected String moneyCurrency;
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar moneyDate;
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar moneyFinishDate;
protected MoneyIO moneyIO;
protected Integer moneyId;
protected String moneyName;
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar moneyNextDate;
protected Integer moneyNumber;
protected String moneyPurpose;
protected MoneyNextTimeType moneyRenewal;
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar moneyTs;
// Getters and Setters for above
...
}
all XMLGregorianCalendar is Date in ejb module
In tapestry page class:
#ejb
statefullEjbMoney
#Persist
#Property
List<Money> mylist
void onPrepareForRender() {
mylist=statefullEjbMoney.findAll();
}
In my template I have:
<table t:type="grid" t:source="mylist"
t:rowsPerPage="10" t:pagerPosition="both"
t:exclude="moneyId,moneyIO,moneyNextDate,moneyFinishDate,cliClientId"
t:reorder="moneyName,moneyPurpose,moneyAmount,moneyCurrency,moneyDate">
[Grid here]
</table>
The stacktrace in the glassfish log is:
Render queue error in SetupRender[Expenses:grid.columns]: Bean editor model for logic.ws.Money does not contain a property named 'moneyDate'.
org.apache.tapestry5.ioc.internal.util.TapestryException: Bean editor model for logic.ws.Money does not contain a property named 'moneyDate'. [at classpath:org/apache/tapestry5/corelib/components/Grid.tml, line 6]
...
Caused by: org.apache.tapestry5.ioc.util.UnknownValueException: Bean editor model for logic.ws.Money does not contain a property named 'moneyDate'.
at org.apache.tapestry5.internal.beaneditor.BeanModelImpl.get(BeanModelImpl.java:160)
at org.apache.tapestry5.internal.beaneditor.BeanModelImpl.reorder(BeanModelImpl.java:223)
at org.apache.tapestry5.internal.beaneditor.BeanModelUtils.reorder(BeanModelUtils.java:107)
at org.apache.tapestry5.internal.beaneditor.BeanModelUtils.modify(BeanModelUtils.java:51)
at org.apache.tapestry5.corelib.components.Grid.getDataModel(Grid.java:523)
at org.apache.tapestry5.corelib.components.GridColumns.setupRender(GridColumns.java:112)
at org.apache.tapestry5.corelib.components.GridColumns.setupRender(GridColumns.java)
at org.apache.tapestry5.internal.structure.ComponentPageElementImpl$SetupRenderPhase.invokeComponent(ComponentPageElementImpl.java:179)
at org.apache.tapestry5.internal.structure.ComponentPageElementImpl$AbstractPhase.invoke(ComponentPageElementImpl.java:138)
... 84 more
If anyone has an idea what is wrong please share?

The exception says the Bean Editor Model doesn't have a property called moneyDate, which I've always thought was a bit unclear.
To explain, Tapestry doesn't know what an XMLGregorianCalendar is, so when Tapestry creates the Bean Model for your grid, it helpfully ignores it.
Quick Solution
The quick way to solve this is to provide getters on your Money entity that return standard JDK Calendars, and display these properties in your grid.
So in your entity:
public class Money {
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar moneyDate;
public Calendar getMoneyDataCalendar() {
return moneyDate.toGregorianCalendar();
}
...
}
Then use it in your grid:
<table t:type="grid" t:source="mylist" t:rowsPerPage="10" t:pagerPosition="both"
t:exclude="moneyId,moneyIO,moneyNextDate,moneyFinishDate,cliClientId"
t:reorder="moneyName,moneyPurpose,moneyAmount,moneyCurrency,moneyDateCalendar">
[Grid here] Note 'moneyDate' changed to 'moneyDateCalendar' above [/Grid here]
</table>
Long Solution
If you plan to display lots of XMLGregorianCalendars then the (more involved) solution is to tell Tapestry about XMLGregorianCalendars and how to display them.
First tell Tapestry to add XMLGregorianCalendar to the Bean Model by adding this to your Module class:
#Contribute(DefaultDataTypeAnalyzer.class)
public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class<?>, String> config) {
config.add(XMLGregorianCalendar.class, "xmlGregorianCalendar");
}
Now tell Tapestry how to display the new "xmlGregorianCalendar". Add to your Module class:
#Contribute(BeanBlockSource.class)
public static void contributeDisplayBlocksForGrid(Configuration<BeanBlockContribution> config) {
config.add(new DisplayBlockContribution("xmlGregorianCalendar", "DisplayBlocks", "xmlGregorianCalendar"));
}
You now have to create a page called DisplayBlocks with a component with an id xmlGregorianCalendar:
public class DisplayBlocks {
#Inject
private Locale locale;
#Property
#Environmental
private PropertyOutputContext context;
private final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
public DateFormat getDateFormat(){
return dateFormat;
}
public Date getCalendarDate() {
XMLGregorianCalendar calendar = (XMLGregorianCalendar) context.getPropertyValue();
if (calendar == null)
return null;
return calendar.getTime();
}
}
<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<t:block id="xmlGregorianCalendar">
<t:output value="calendarDate" format="dateFormat"/>
</t:block>
</t:container>
For more info look at the Tapestry source code and see how Tapestry configures itself. In particular look at:
org.apache.tapestry5.services.TapestryModule.contributeDefaultDataTypeAnalyzer()
org.apache.tapestry5.services.TapestryModule.provideDefaultBeanBlocks()
org.apache.tapestry5.corelib.pages.PropertyDisplayBlocks

There are minimal changes I have made to make it work
description is below.
Appmodule.java added following methods
public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class<?>, String> config) {
config.add(XMLGregorianCalendar.class, "XMLGregorianCalendar");
}
#Contribute(BeanBlockSource.class)
public static void contributeDisplayBlocksForGrid(Configuration<BeanBlockContribution> config) {
config.add(new DisplayBlockContribution("XMLGregorianCalendar", "DisplayBlocks", "XMLGregorianCalendar"));
}
no #Contribute on method contributeDefaultDataTypeAnalyzer
DisplayBlocks.java
public class DisplayBlocks {
#Inject
private Locale locale;
#Property
#Environmental
private PropertyOutputContext context;
private final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
public DateFormat getDateFormat() {
return dateFormat;
}
public Date getCalendarDate() {
XMLGregorianCalendar date = (XMLGregorianCalendar) context.getPropertyValue();
Calendar calendar = date.toGregorianCalendar();
if (calendar == null) {
return null;
}
return calendar.getTime();
}
XMLGregorianCalendar to context.getPropertyValue();
Than to Calender
DisplayBlocks.tml
<t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<t:block id="XMLGregorianCalendar">
<t:output value="calendarDate" format="dateFormat"/>
</t:block>
</t:container>
Changed block id to XMLGregorianCalendar

I got an error like this and reached this page. As it is clear from the error you should easily add a public getter for your property.

Related

Passing Arguments to classes extending the service class Java

I was wondering if there was a way to pass arguments the a class extending the Service class from the Javafx concurrent package. I would like ProteinariumThread to take in a String argument like ClusterID seen below:
public class ProteinariumThread extends Service<String>{
String ClusterID = "q";
#Override
protected Task<String> createTask(){
return new Task<String>() {
#Override
protected String call() throws Exception {
updateMessage("Running Proteinarium");
System.out.println("Asleep");
ProteinariumRun.PRun(ClusterID);
System.out.println("Woke Up");
String woke = "Woke Up";
return woke;
}
};
}
}
Currently in order to run this background task I use the following bit of code:
final ProteinariumThread service = new ProteinariumThread();
service.start();
This however does not allow me to take in a String argument. Is there anyway to make it that service.start() is able to take in String arguments so that String variable ClusterID can come from outside of the ProteinariumThread class?
final ProteinariumThread service = new ProteinariumThread();
service.start(ClusterID);
You just need to give your service class a constructor and/or method which accepts the necessary argument. As services are meant to be reusable, it'd probably be best to allow configuration throughout the service's lifetime by exposing a property:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class ProteinariumService extends Service<String> {
private final StringProperty clusterId = new SimpleStringProperty(this, "clusterId");
public final void setClusterId(String clusterId) { this.clusterId.set(clusterId); }
public final String getClusterId() { return clusterId.get(); }
public final StringProperty clusterIdProperty() { return clusterId; }
public ProteinariumService() {}
public ProteinariumService(String clusterId) {
setClusterId(clusterId);
}
#Override
protected Task<String> createTask() {
return new Task<>() {
final String clusterId = getClusterId(); // cache configuration
#Override
protected String call() throws Exception {
...
}
};
}
}
It's important you copy the needed state from the service to the task since the task is executed on a background thread.
Then when you need to change the cluster ID you just do:
// or bind the property to something in the UI (e.g. a TextField)
theService.setClusterId(newClusterId);
theService.start();
If you really want to be able to do that in one line you can always define an overload for start in your service class:
public void start(String clusterId) {
setClusterId(clusterId):
start();
}

Automapper ninject dependencies

I have a problem with the Automapper on my website and I can't find a solution.
I've created a class called AutoMapperProfile where I'd like to put all my Maps
public class AutoMapperProfile: Profile
{
private readonly IConfiguration _mapper;
public AutoMapperProfile(IConfiguration mapper)
{
_mapper = mapper;
}
protected override void Configure()
{
base.Configure();
_mapper.CreateMap<SlideDTO, Slide>();
_mapper.CreateMap<Slide, SlideDTO>();
}
}
For DI purposes I'm using Ninject, so I've added the following bindings in NinjectWebCommon:
kernel.Bind<IMappingEngine>().ToMethod(ctx => Mapper.Engine);
kernel.Bind<IConfigurationProvider>().ToMethod(x => Mapper.Engine.ConfigurationProvider);
The controller looks like this:
private readonly ISlideRepository slideRepository;
private readonly IMappingEngine mappingEngine;
public HomeController(
ISlideRepository slideRepository,
IMappingEngine mappingEngine)
{
this.slideRepository = slideRepository;
this.mappingEngine = mappingEngine;
}
[HttpGet]
public ActionResult Index()
{
var model = new IndexViewModel();
var slide = slideRepository.GetSlide();
model.Slide = mappingEngine.Map<SlideDTO, Slide>(slide);
return View(model);
}
When I map from SlideDTO to Slide I get the following error:
Missing type map configuration or unsupported mapping.
So my best guess is that I didn't do the binds correctly so that Automapper can see my maps, but I'm not sure how can I fix it.
You don't need to inject IConfiguration into AutoMapperProfile, it already inherits a CreateMap method from Profile.
Make sure that AutoMapperProfile has a parameterless constructor like this:
public class AutoMapperProfile : Profile
{
protected override void Configure()
{
this.CreateMap<SlideDTO, Slide>();
this.CreateMap<Slide, SlideDTO>();
}
}
And then you need to make sure that AutoMapper knows about this profile, here is how you can do it:
Mapper.Engine.ConfigurationProvider.AddProfile<AutoMapperProfile>();
Please note that you can invoke the AddProfile method on any IConfigurationProvider (if you decide not to use the global ConfigurationProvider and Engine).

Add action to a cancel button in an editable grid in vaadin

I'm working with an editable Grid with Vaadin 7. When a row is in edit mode, it shows two buttons: save and cancel.
(Just in case, the image was taken from here Book of Vaadin)
With the:
grid.getEditorFieldGroup().addCommitHandler(new CommitHandler() {
private static final long serialVersionUID = 1L;
#SuppressWarnings("unchecked")
#Override
public void preCommit(CommitEvent commitEvent) throws CommitException{}
#Override
public void postCommit(CommitEvent commitEvent) throws CommitException{}
});
I can do something in the save action.
But, can I do something like that with the cancel action?
Thank you.
This is a serious deficiency of the component. According to the forum, they're working on it, but for the time being it seems that the most straightforward way is to extend the Grid component and override the doCancelEditor method. Here's a snippet:
public class MyGrid extends Grid {
protected Object newRow;
#Override
protected void doCancelEditor() {
super.doCancelEditor();
getContainerDataSource().removeItem(newRow);
setEditorEnabled(false);
}
public void setNewRow(Object newRow) {
this.newRow = newRow;
}
Note that you have to tell the MyGrid object when you create the row. Also, note that you're extending the server side, so you don't have to alter the client (widget code), but you do need to refer to the new component in your UI design.
Actually, saveEditor() should be also overridden, as doCancelEditor() seems to be invoked on save action, too. My code:
public class MyGrid extends Grid {
private boolean addingMode = false;
private JPAContainer<SomeEntity> container;
private Object recentlyAddedItemID;
public MyGrid(Indexed indexed) {
container = indexed;
}
#Override
protected void doCancelEditor() {
Object id = getEditedItemId();
super.doCancelEditor();
if (addingMode) {
getContainerDataSource().removeItem(id);
recentlyAddedItemID = null;
}
addingMode = false;
}
#Override
public void saveEditor() throws FieldGroup.CommitException {
if (addingMode) recentlyAddedItemID = getEditedItemId();
addingMode = false;
super.saveEditor();
}
public Object getRecentlyAddedItemID() {
return recentlyAddedItemID;
}
public void addNewElement(SomeEntity entity) {
addingMode = true;
editItem(container.addEntity(entity));
}
}
MyGrid grid = new MyGrid(JPAContainerFactory.make(SomeEntity.class, entityManager));
grid.addNewElement(new SomeEntity());
/*
if we want to know the new items's ID (actually the new primary key
in case of JPAContainer), we can check it by:
*/
Object id = grid.getRecentlyAddedItemID();
/*
returns null if editor was cancelled and finally nothing new was persisted
*/

SpringWeb Jmustache and #DateTimeFormat

I have a spring boot application using server-side Mustache-Templates (JMustache).
A simple Bean with an #DateTimeFormat-Annotation:
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
public class GeneralInformation {
private Date serverTime = new Date();
#DateTimeFormat(pattern="dd.MM.yyyy")
public Date getServerTime() {
return serverTime;
}
public void setServerTime(Date serverTime) {
this.serverTime = serverTime;
}
}
A simple controller adding the bean to the model:
#Controller
#RequestMapping(value="/")
public class RootController {
// some Autowiring stuff here...
#RequestMapping(value="")
public String index(Model model){
model.addAttribute("generalInformation", new GeneralInformation());
return "hello";
}
}
And my Server-Side Mustache-template stored under templates/hello.html
<p>Servertime: {{generalInformation.serverTime}}</p>
When using JSP's the output of the date is formatted after the pattern used in the #DateTimeFormat-Annotation but not when using my Mustache-Template.
I could format the date in the #Controller-Annotated-Method and store it as a String in the Bean, but that doesn't seem to be a good way.
Does anybody know, if it is possible to make JMustache aware of the Validation-Tags?
How else could I achieve Formatting when using JMustache together with SpringMVC?
#DateTimeFormat only works with JSP

Grails bind request parameters to enum

My Grails application has a large number of enums that look like this:
public enum Rating {
BEST("be"), GOOD("go"), AVERAGE("av"), BAD("ba"), WORST("wo")
final String id
private RateType(String id) {
this.id = id
}
static public RateType getEnumFromId(String value) {
values().find {it.id == value }
}
}
If I have a command object such as this:
class MyCommand {
Rating rating
}
I would like to (for example) automatically convert a request parameter with value "wo" to Rating.WORST.
The procedure for defining custom converters is described here (in the context of converting Strings to Dates). Although this procedure works fine, I don't want to have to create a class implementing PropertyEditorSupport for each of my enums. Is there a better alternative?
I found a solution I'm pretty happy with.
Step 1: Create an implementation of PropertyEditorSupport to convert text to/from the relevant Enum
public class EnumEditor extends PropertyEditorSupport {
private Class<? extends Enum<?>> clazz
public EnumEditor(Class<? extends Enum<?>> clazz) {
this.clazz = clazz
}
public String getAsText() {
return value?.id
}
public void setAsText(String text) {
value = clazz.getEnumFromId(text)
}
}
Step 2: Define a class that registers EnumEditor as a converter for the various enum classes. To change the list of enum classes that are bindable by id, just modify BINDABLE_ENUMS
public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
private static final String REQUIRED_METHOD_NAME = 'getEnumFromId'
// Add any enums that you want to bind to by ID into this list
private static final BINDABLE_ENUMS = [Rating, SomeOtherEnum, SomeOtherEnum2]
public void registerCustomEditors(PropertyEditorRegistry registry) {
BINDABLE_ENUMS.each {enumClass ->
registerEnum(registry, enumClass)
}
}
/**
* Register an enum to be bound by ID from a request parameter
* #param registry Registry of types eligible for data binding
* #param enumClass Class of the enum
*/
private registerEnum(PropertyEditorRegistry registry, Class<? extends Enum<?>> enumClass) {
boolean hasRequiredMethod = enumClass.metaClass.methods.any {MetaMethod method ->
method.isStatic() && method.name == REQUIRED_METHOD_NAME && method.parameterTypes.size() == 1
}
if (!hasRequiredMethod) {
throw new MissingMethodException(REQUIRED_METHOD_NAME, enumClass, [String].toArray())
}
registry.registerCustomEditor(enumClass, new EnumEditor(enumClass))
}
}
Step 3: Make Spring aware of the registry above by defining the following Spring bean in grails-app/conf/spring/resources.grooovy
customPropertyEditorRegistrar(CustomPropertyEditorRegistrar)
So the default Databinding binds on the Enum name and not a separately defined property of the Enum. You can either create your own PropertyEditor as you have mentioned or do a work-around similar to this:
class MyCommand {
String ratingId
Rating getRating() {
return Rating.getEnumFromId(this.ratingId)
}
static constraints = {
ratingId(validator:{val, obj -> Rating.getEnumFromId(val) != null })
}
}

Resources