Apache Isis: #Property(editing = Editing.ENABLED) doesn't work for ViewModels - isis

I added a property to ViewModel and marked it with Editing.ENABLED.
#DomainObject(
nature = Nature.VIEW_MODEL,
objectType = "homepage.HomePageViewModel"
)
public class HomePageViewModel {
#Setter #Getter
#Property(editing = Editing.ENABLED)
private String editableField;
}
But this field is not editable on UI:
But it works fine for SimpleObject:
Does it work correctly for ViewModel?
Maybe ViewModel shouldn't have any properties?

No, it isn't working correctly for view models... the framework is meant to support this.
The good news is that there is a workaround. If you annotate the class to use the (more flexible) JAXB-style of view model, then it all works as expected.
Here's an updated version of the class; look for annotations starting #Xml...:
#XmlRootElement(name = "compareCustomers")
#XmlType(
propOrder = {
"editableField"
}
)
#XmlAccessorType(XmlAccessType.FIELD)
public class HomePageViewModel {
#XmlElement(required = true)
#Setter #Getter
#Property(editing = Editing.ENABLED)
private String editableField;
public TranslatableString title() {
return TranslatableString.tr("{num} objects", "num", getObjects().size());
}
public List<SimpleObject> getObjects() {
return simpleObjectRepository.listAll();
}
#XmlTransient
#javax.inject.Inject
SimpleObjectRepository simpleObjectRepository;
}
For more on JAXB view models, see the user guide.
Meantime I've raised a JIRA ticket for the issue you've discovered,

Related

Has optaplanners toList ConstraintCollector a bug?

When using toList() ConstraintCollector in optaplanner 8.1 like:
factory.from(Lesson.class)
.groupBy(Lesson::getCourse, ConstraintCollectors.toList()).penalize(...);
I run into:
Exception executing consequence for rule "foo" in model: java.lang.ClassCastException: class model.Lesson cannot be cast to class java.util.List (model.Lesson is in unnamed module of loader 'app'; java.util.List is in module java.base of loader 'bootstrap')
at org.drools.core.runtime.rule.impl.DefaultConsequenceExceptionHandler.handleException(DefaultConsequenceExceptionHandler.java:39)
Question: Is this a optaplanner bug, or is my code wrong?
The most simple complete reproducer I find is:
#PlanningSolution
public class OptaplannerIssue2 implements ConstraintProvider {
#Override
public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] {factory.from(IssueEntity.class)
.groupBy(IssueEntity::getValue, ConstraintCollectors.toList())
.penalize("x", HardSoftScore.ofHard(1), (entity, enityList) -> 2)};
}
#PlanningScore
private HardSoftScore score = HardSoftScore.ZERO;
#PlanningEntityCollectionProperty
private final List<IssueEntity> entities = new ArrayList<IssueEntity>();
public List<IssueEntity> getEntities() {
return entities;
}
#ValueRangeProvider(id = "valueRange")
public CountableValueRange<Integer> getValueRange() {
return ValueRangeFactory.createIntValueRange(0, 4);
}
public static void main() {
// create Entity
OptaplannerIssue2 issue = new OptaplannerIssue2();
IssueEntity e1 = new IssueEntity();
issue.entities.add(e1);
// solve
SolverFactory<OptaplannerIssue2> solverFactory = SolverFactory.create(new SolverConfig()
.withEnvironmentMode(EnvironmentMode.FULL_ASSERT).withSolutionClass(OptaplannerIssue2.class)
.withEntityClasses(IssueEntity.class)
.withScoreDirectorFactory(
new ScoreDirectorFactoryConfig().withConstraintProviderClass(OptaplannerIssue2.class))
.withTerminationConfig(new TerminationConfig().withSecondsSpentLimit(5L)).withPhases(
new ConstructionHeuristicPhaseConfig()
.withConstructionHeuristicType(ConstructionHeuristicType.FIRST_FIT),
new LocalSearchPhaseConfig().withLocalSearchType(LocalSearchType.LATE_ACCEPTANCE)));
Solver<OptaplannerIssue2> solver = solverFactory.buildSolver();
solver.solve(issue);
}
}
With the following entity-class:
#PlanningEntity
public class IssueEntity {
#PlanningVariable(valueRangeProviderRefs = {"valueRange"})
Integer value;
public Integer getValue() {
return value;
}
}
In the related thread: Optaplanner GroupBy with toList not working as expected the questioner didn't provide all information to commentators trying to help and when I provided reproducer there I got deleted, so I had to ask new question.
The behavior you describe is a bug in OptaPlanner, which we have now fixed. Please upgrade to the next release of OptaPlanner, which at the time of writing this answer will be OptaPlanner 8.2.0.
For details, see PLANNER-2305.

Can I specify the jackson #JsonView to use for method result transformation in RestEasy?

I'm working with a serialization model based on #JsonView. I normally configure jackson with a ContextResolver like this:
#Override
public ObjectMapper getContext(Class<?> aClass) {
// enable a view by default, else Views are not processed
Class view = Object.class;
if (aClass.getPackage().getName().startsWith("my.company.entity")) {
view = getViewNameForClass(aClass);
}
objectMapper.setSerializationConfig(
objectMapper.getSerializationConfig().withView(view));
return objectMapper;
}
This works fine if I serialize single entities. However, for certain use cases I want to serialize lists of my entities using the same view as for single entities. In this case, aClass is ArrayList, so the usual logic doesn't help much.
So I'm looking for a way to tell Jackson which view to use. Ideally, I'd write:
#GET #Produces("application/json; charset=UTF-8")
#JsonView(JSONEntity.class)
public List<T> getAll(#Context UriInfo uriInfo) {
return getAll(uriInfo.getQueryParameters());
}
And have that serialized under the view JSONEntity. Is this possible with RestEasy? If not, how can I emulate that?
Edit: I know I can do the serialization myself:
public String getAll(#Context UriInfo info, #Context Providers factory) {
List<T> entities = getAll(info.getQueryParameters());
ObjectMapper mapper = factory.getContextResolver(
ObjectMapper.class, MediaType.APPLICATION
).getContext(entityClass);
return mapper.writeValueAsString(entities);
}
However, this is clumsy at best and defeats the whole idea of having the framework deal with this boilerplate.
Turns out, it is possible to simply annotate a specific endpoint with #JsonView (just as in my question) and jackson will use this view. Who would have guessed.
You can even do this in the generic way (more context in my other question), but that ties me to RestEasy:
#Override
public void writeTo(Object value, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHd,
OutputStream out) throws IOException {
Class view = getViewFromType(type, genericType);
ObjectMapper mapper = locateMapper(type, mediaType);
Annotation[] myAnn = Arrays.copyOf(annotations, annotations.length + 1);
myAnn[annotations.length] = new JsonViewQualifier(view);
super.writeTo(value, type, genericType, myAnn, mediaType, httpHd, out);
}
private Class getViewFromType(Class<?> type, Type genericType) {
// unwrap collections
Class target = org.jboss.resteasy.util.Types.getCollectionBaseType(
type, genericType);
target = target != null ? target : type;
try {
// use my mix-in as view class
return Class.forName("example.jackson.JSON" + target.getSimpleName());
} catch (ClassNotFoundException e) {
LOGGER.info("No view found for {}", target.getSimpleName());
}
return Object.class;
}

getResultList() generate org.hibernate.TransientObjectException: object references an unsaved transient instance

I'm aware that this problem has been asked several times before. But I believe I have a very different case, I'm currently encountering this error when invoking the getResultList() method, not when persisting.
Note that I'm using:
-javaee6
-seam3-security
-jboss7.1.3
-postgresql
Series of events:
1.) After login, I saved my user in picketlink using the interface org.picketlink.idm.api.User:
setUser(new MyUser(user));
I should be able to retrieve the user using ((MyUser)identity.getuser()).getUser();
2.) Now I have a BusinessAccount entity that has OneToMany relationship with AccounType:
#Entity
#Table(name = "T_ACCOUNT")
public class BusinessAccount extends BaseEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "ACCOUNT_TYPE_ID")
private AccountType accountType;
}
#Entity()
#Table(name = "T_ACCOUNT_TYPE")
public class AccountType extends BaseEntity {
#Enumerated(EnumType.STRING)
#Column(name = "ACCOUNT_TYPE", length = 20)
private AccountTypeEnum accountTypeEnum;
}
My problem is I have a service: BusinessAccountService that extends BaseService, which calls getAccounts and it throws this error:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
BaseService has:
public User getCurrentUser() {
if(currentUser==null){
try {
currentUser=((MyUser) identity.getUser()).getUser();
} catch(Exception e){
log.warn("getCurrentUser cannot retrieve current user from session identity and currentUser has not been set programmatically");
}
}
return currentUser;
}
And then my dynamically generated query is like this:
select distinct a from BusinessAccount a where a.accountType.accountTypeEnum=:a_accountType_accountTypeEnum and (a.accountType in (:a_accountType0))
Param name:a_accountType_accountTypeEnum value:serviceProvider
Param name:a_accountType0 value:[org.model.accounts.AccountType#1, org.model.accounts.AccountType#2]
and I found out that this line is throwing the error:
and (a.accountType in (:a_accountType0))
Why is that? When I'm only executing a getResultList()?
I think I've found the problem, the AccountType is extending a BaseEntity that has a Version field. When I removed the extend and add Version property in AccountType, I got the same error.
The Version field:
#Version
#Column(name = "VERSION")
private Integer version;
This version field must have an initial value which is 0.

Why do not #JsonTypeInfo work together with #JsonIdentityInfo?

#JsonIdentityInfo works as expected with the following classes:
Baseclass:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uuid")
public class TestEntityBas {
#JsonProperty
public String uuid = "0001";
}
Subclass:
public class TestEntityGoa extends TestEntityBas {
#JsonProperty
public String texten = "This is text!";
}
Container class:
public class TestEntity {
#JsonProperty
String stringer = "Hej hopp!";
#JsonIdentityReference(alwaysAsId = true)
public TestEntityGoa goa = new TestEntityGoa();
}
The result is as expected:
{"stringer":"Hej hopp!","goa":"0001"}
When I add #JsonTypeInfo to the base class like this:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uuid")
public class TestEntityBas {
#JsonProperty
public String uuid = "0001";
}
Now the entire TestEntityGoa get serialized like this:
{"stringer":"Hej hopp!","goa":{"#class":"com.fodolist.model.TestEntityGoa","uuid":"0001","texten":"This is text!"}}
I expect the first result even when I use #JsonTypeInfo and #JsonIdentityInfo in the same class. What am I doing wrong?
I can't see anything obviously wrong here, so you may have found a bug. Combination of type and identity info is bit tricky to handle so there may be edge cases that do not yet work as intended, so could you file a bug at Github issue tracker for this?

Jackson vector serialization exception

I have the following code with a simple class and a method for writing and then reading:
ObjectMapper mapper = new ObjectMapper();
try{
DataStore testOut = new DataStore();
DataStore.Checklist ch1 = testOut.addChecklist();
ch1.SetTitle("Checklist1");
String output = mapper.writeValueAsString(testOut);
JsonNode rootNode = mapper.readValue(output, JsonNode.class);
Map<String,Object> userData = mapper.readValue(output, Map.class);
}
public class DataStore {
public static class Checklist
{
public Checklist()
{
}
private String _title;
public String GetTitle()
{
return _title;
}
public void SetTitle(String title)
{
_title = title;
}
}
//Checklists
private Vector<Checklist> _checklists = new Vector<Checklist>();
public Checklist addChecklist()
{
Checklist ch = new Checklist();
ch.SetTitle("New Checklist");
_checklists.add(ch);
return ch;
}
public Vector<Checklist> getChecklists()
{
return _checklists;
}
public void setChecklists(Vector<Checklist> checklists)
{
_checklists = checklists;
}
}
The line:
String output = mapper.writeValueAsString(testOut);
causes an exception that has had me baffled for hours and about to abandon using this at all.
Any hints are appreciated.
Here is the exception:
No serializer found for class DataStore$Checklist and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: DataStore["checklists"]->java.util.Vector[0])
There are multiple ways to do it, but I will start with what you are doing wrong: your naming of getter and setter method is wrong -- in Java one uses "camel-case", so you should be using "getTitle". Because of this, properties are not found.
Besides renaming methods to use Java-style names, there are alternatives:
You can use annotation JsonProperty("title") for GetTitle(), so that property is recognized
If you don't want the wrapper object, you could alternatively just add #JsonValue for GetTitle(), in which case value used for the whole object would be return value of that method.
The answer seems to be: You can't do that with Json. I've seen comments in the Gson tutorial as well, that state that some serialization just doesn't work. I downloaded XStream and spat it out with XML in a few minutes of work and a lot less construction around what I really wanted to persist. In the process, I was able to delete a lot of code.