I'm developing a website with an inline editor using Play framework 2.5 and Ebean as ORM, and I have a news section where the admin can edit every single news (editing fields inline such as title, content and so on).
In order to do so, I set every html element which can be modified with an id equals to the news model field (e.g. the html element mapping the field title will have id="title"), then when I receive data from the client, I use reflection on the controller to map every content with the correct news field.
Here is the code (EditContent is an object which contains informations like the id and the htmlContent of every modified content):
News news = News.find.byId(newsId);
for(EditContent content : pageContents.contents) {
Field field = news.getClass().getField(content.cssId);
field.setAccessible(true);
field.set(news, content.htmlContent);
}
news.update();
The problem is that the update seems to be executed, but actually values are not updated on db. Using debugger, I inspected the object news and I can see that the fields are modified properly, but then the update has no effects on db.
Also, I noticed that the same code using:
News news = new News()
...
//reflection to save modifed contents in the new object
...
news.save()
works as I expect, saving a new row in the database.
Any idea?
Thank you in advance for your help!
You are setting the field values rather than calling a setter method.
So for the update() ... Ebean is not aware of which properties have been changed - it thinks none have changed.
Play modifies field put calls into method calls via enhancement. So this is possibly why you think these reflection field set values might work.
So basically, as #Rob Bygrave said... The setter method should be invoked here rather than setting the field value directly cause the ebean will ignore the new value if you set the value to the corresponding field directly. It seems that the play framework following the Java bean convention, so basically we can guess what the set name called.
Here is an example code to update User's information dynamically:
private final String[] userUpdatableNames = { "name", "password", "allowGPS" };
...
JsonNode dateForm = request().body().asJson();
Field field;
Class<?> type;
Method method;
for (int i = 0; i < userUpdatableNames.length; i++) {
if (isArgs[i]) {
try {
field = target.getClass().getDeclaredField(userUpdatableNames[i]);
type = field.getType();
Method method = target.getClass().getMethod("set" + initialUpperize(userUpdatableNames[i]), type);
method.invoke(target, convert(type,dateForm.findValue(userUpdatableNames[i]).textValue()));
}catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException
| NoSuchMethodException | InvocationTargetException e) {
return internalServerError(Json.toJson("Invoke exception"));
}
}
}
...
public String initialUpperize(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
...
private Object convert(Class<?> targetType, String text) {
PropertyEditor editor = PropertyEditorManager.findEditor(targetType);
editor.setAsText(text);
return editor.getValue();
}
where isArgs is a boolean array to mark whether the field is in Json body...
Thanks
Related
I'm using https://github.com/leangen/graphql-spqr with spring-boot java application. I can reach to alias name easily but how can I reach to original fieldName?
class Food {
#GraphQLQuery(name = "aliasNameX", description = "A food's name")
private String originalName;
...
}
....
#GraphQLQuery(name = "foods") // READ ALL
#Override
public List<Food> getFoods(#GraphQLEnvironment ResolutionEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.dataFetchingEnvironment.getSelectionSet();
List<SelectedField> fields = selectionSet.getFields();
for (SelectedField f: fields)
{
System.out.println(f.getName());
}
return foodRepository.findAll();
}
When I run this code, Output looks like with alias fields: "aliasNameX", ..., but I need original name like "originalName". Is there a way to do it?
Solved, according to:
https://github.com/leangen/graphql-spqr/issues/381
Posting my original answer here as well.
You want the underlying field names, but from a level above. Still possible, but ugly :(
for (SelectedField selectedField : env.dataFetchingEnvironment.getSelectionSet().getImmediateFields()) {
Optional<Operation> operation = Directives.getMappedOperation(selectedField.getFieldDefinition());
String javaName = operation.map(op -> ((Member) op.getTypedElement().getElement()).getName()).orElse(null);
}
Be very careful though. If there's more than one Java element exposed per GraphQL field, getTypedElement().getElement() will explode. So to be sure you'd have to call getTypedElement().getElements() (plural) instead and decide what to do. ClassUtils#getPropertyMembers might also be useful, or the ClassUtils.findXXX family of methods.
You'd basically have to do this:
List<AnnotatedElement> elements = getTypedElement().getElements();
//Look for a field and use its name
Optional<String> field = Utils.extractInstances(elements, Field.class).findFirst().map(Field::getName);
//Look for a getter and find its associated field name
Optional<String> getter = Utils.extractInstances(elements, Method.class).findFirst().map(ClassUtils::getFieldNameFromGetter);
This API might have to change in future, as SDL-based tools are proliferating, so complex directives like the ones SPQR is using are causing problems...
I have an app with several #MappedSuperClasses. Out of one of them I need to write a csv with columns in a very particular order stablished by the client.
Doing a Entity.class.getDeclaredFields() used to be enough to retrieve and write the columns in the right order before we had superclasses, but now, even if I use a custom solution to iterate through the superclasses's fields the order is incorrect, so I resorted to using a DTO Entity which returns the right order when calling getDeclaredFields().
The problems come when I try to retrieve the values present in the entities related, we used to do something like:
Object value = getLineFromField(field, line);
Where getLineFromField() method would be like:
private Object getLineFromField(Field field, Entity line) {
Object value = null;
try {
value = field.get(line);
} catch (Exception e) {
LOG.info("There is no value. Adding a WhiteSpace to the Column Value");
}
return value;
}
The problem appears in the field.get(line), this method from the Field library will always return a null value
Any experience out there doing a similar mapping?
Just trying to avoid writing a super-ugly 100-liner switch case in the codebase...
EDIT to add internal exception I get from the Field library: UnsafeObjectFieldAccessorImpl
I want to convert an object of another format into a protobuf, knowing the protobuf's Descriptors. It's easy to do for regular fields or even a nested field. But, I'm running into a problem for repeated fields.
message Foo {
optional MsgA a = 1;
repeated MsgB b = 2;
}
For "MsgA a", the code bld.getFieldBuilder(field) works:
Foo.Builder bld = Foo.newBuilder();
Descriptors.Descriptor msgDesc = Foo.getDescriptor();
List<Descriptors.FieldDescriptor> fields = msgDesc.getFields();
for (Descriptors.FieldDescriptor field : fields) {
Message.Builder subBld = bld.getFieldBuilder(field);
// set foreign value xyz using subBld
// subBld.setFleld(subfield1, xyz);
}
But for "MsgB b", the same code throws "UnsupportedOperationException: getFieldBuilder() called on a non-Message type."
I understand the repeated field is a list, I may set each one separately. But, how do I get a builder first? Is there a clean and easy way to do the similar?
Thanks for any input.
You don't get a builder for the repeated field itself - you call Builder.addRepeatedField(field, value) etc. To get a builder for the type of the repeated field, you can use:
Builder builder = bld.newBuilderForField(field)
If you want to modify an existing value, you can use Builder.getRepeatedFieldBuilder(field, index).
To create an instance to start with, you can use Builder.newBuilderForField:
Message.Builder subBld = bld.newBuilderForField(field);
// Now modify subBld, then...
bld.addRepeatedField(field, subBld.build());
I want to set the value of an object EObject knowing it's EAttribute. Is that possible?
I can use reflections, build the method name and invoke it, but is there a better way to achieve that? Maybe some EMF Util classes?
public static Object invokeMethodBy(EObject object, EAttribute attribute, Object...inputParameters){
String attrName = attribute.getName().substring(0, 1).toUpperCase() + attribute.getName().substring(1);
Object returnValue = null;
try {
returnValue = object.getClass().getMethod("set"+attrName, boolean.class).invoke(object,inputParameters);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException
| SecurityException e1) {
e1.printStackTrace();
}
return returnValue;
}
EMF has already its own introspection mechanisms, that do not use Java Reflection but use static generated code.
What you need is this:
object.eSet(attribute, value);
If the attribute is a "many" relationship, such as a List, you need to retrieve the list before and then add the content to the list:
if (attribute.isMany()) {
List<Object> list = (List<Object>) object.eGet(attribute);
list.addAll(value);
}
In case you don't have the EAttribute but have the name of the attribute (as String) you can also retrieve the EStructuralFeature by name using the EClass metadata:
EStructuralFeature feature = object.eClass.getEStructuralFeature(attributeName);
object.eSet(feature, value);
You should look to the EObject API, in special the methods starting by "e". EcoreUtil class has also useful methods.
I am trying to save an Enum field to database but I am having a problem mapping the field to database. The code I have is as follows:
public enum InvoiceStatus {
PAID,
UNPAID;
}
and I am using this enum in one of my application classes as follows:
public class Invoice {
Enumerated(EnumType.ORDINAL)
#Column(name="INVOICE_STATUS", nullable = false, unique=false)
private InvoiceStatus invoiceStatus;
}
finally I let the app user select the Invoice Status from the view (JSP) using a drop down menu.
But I am not sure how to map the value received from the drop down menu selection to the Invoice Status field
I tried mapping the value received to short as follows, but it won't compile
invoice.setInvoiceStatus(Short.parseShort(request.getParameter("inbStatus")));
can someone please tell me how to map the data received from the view to the enum field?
Enum ordinal values are zero based indexes. In your case:
PAID = 0
UNPAID = 1
So the following code will return PAID:
int invoiceStatus = 0;
invoice.setInvoiceStatus(InvoiceStatus.values()[invoiceStatus]);
And the following code will return UNPAID:
int invoiceStatus = 1;
invoice.setInvoiceStatus(InvoiceStatus.values()[invoiceStatus]);
That means you should be able to do this way:
short invoiceStatus = Short.parseShort(request.getParameter("inbStatus"));
invoice.setInvoiceStatus(InvoiceStatus.values()[invoiceStatus]);
But only if inbStatus is 0 or 1. You should always validate user input for null and invalid values.
I see that u are using
Enumerated(EnumType.ORDINAL)
however after a while it could be quite difficult to troubleshoot if your enum will grow. Another issue with the ordinal is that you could refactor your code and change the order of the enum values and after that you could be in trouble. Mainly if it is a shared codebase and someone just decides to cleanup the code and "group the relevant enum constants together". If you'll use:
Enumerated(EnumType.STRING)
Directly the enum "name" will be inserted into the database. (Therefore you need Varchar type). If you want to present more user friendly version of your enum you could probably have:
public enum InvoiceStatus {
PAID(0, "Paid"), UNPAID(1, "Unpaid"), FAILED(2, "Failed"), PENDING(3, "Pending");
private int st;
private in uiLabel;
private InvoiceStatus(int st, String uiLabel){
this.st = st;
this.uiLabel = uiLabel;
}
private Map<String, InvoiceStatus> uiLabelMap = new HashMap<String, InvoiceStatus> ();
static {
for(InvoiceStatus status : values()) {
uiLableMap.put(status.getUiLabel(), status);
}
}
/** Returns the appropriate enum based on the String representation used in ui forms */
public InvoiceStatus fromUiLabel(String uiLabel) {
return uiLableMap.get(uiLabel); // plus some tweaks (null check or whatever)
}
//
// Same logic for the ORDINAL if you are keen to use it
//
}
Probably this could be also a solution for your problem, however i would really not use the ORDINAL based mapping. But just personal feeling.