How to get a protobuf repeated field builder in Java? - java

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());

Related

HttpServer Request get date range from query string

I am new to Java and Vertx and I have a query string with the following format:
GET /examples/1/data?date_1[gt]=2021-09-28&date_1[lt]=2021-10-28
Here I have this date_1 parameter which is within a certain range. I have been using HttpServerRequest class to extract simple parameters like integers but not sure how to proceed with these kind of range parameters.
With the simple parameters, I can do something like:
String param = request.getParam(paramName);
paramAsInteger = Integer.valueOf(paramAsString);
However, confused as to how to deal with the gt and lt options and the fact that we have same parameter twice.
You say that you have difficulties parsing out these tokens. Here's how you can handle this.
The first thing to understand is that the parameter name is NOT "date1"
There are actually two parameters here
2.1. "date_1[gt]" with a value of "2021-09-28"
2.2. "date_1[lt]" with a value of "2021-10-28"
This is because in the URI parameter definition everything before the "=" sign is the parameter name and everything after is the parameter value.
You can just do
String dateAsString = request.getParam("date1[gt]");
paramAsInteger = toDate(dateAsString)
To implement the toDate() function read this simple article how to convert a string object into a data object using a standard library
(link)
Vert.x will treat these parameters as two separate ones. So RoutingContext#queryParam("date_1[gt]") will only give you the value for [gt]. If you want the value for [lt] you need to get that separately.
That being said, you can move this tedious logic into an extra handler and store the values in the RoutingContext. Something like this might be easier:
private void extractDates(RoutingContext ctx) {
var startDate = ctx.queryParam("date_1[gt]");
var endDate = ctx.queryParam("date_1[lt]");
var parsedStartDate = DateTimeFormatter.ISO_LOCAL_DATE.parse(startDate.get(0));
var parsedEndDate = DateTimeFormatter.ISO_LOCAL_DATE.parse(endDate.get(0));
// things we put in the context here can be retrieved by later handlers
ctx.put("startDate", parsedStartDate);
ctx.put("endDate", parsedEndDate);
ctx.next();
}
Then, in your actual handler you can access the two dates as follows:
router.get("/date")
.handler(this::extractDates)
.handler(ctx -> {
var responseBody = ctx.get("startDate") + " - " + ctx.get("endDate");
ctx.end(responseBody);
});
This allows you to keep your actual business logic concise.

Get Original Field Name on GraphQL

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...

How to duplicate fields names with proto3 oneof feature?

Proto3 supports the oneof features, where you can have a message with many fields and where at most one field will be set at the same time.
Since one field will be set at a time, it would be reasonable to have duplicate field names in the proto schema. The problem is the proto generater sees this as a redefinition.
I'd like to do this because in my situation, this makes json serialization with JsonFormat simple.
For example, I may like to have
message MyResponse {
int32 a = 1;
string b = 2;
oneof Properties {
PropertiesType1 properties = 3;
PropertiesType2 properties = 4;
PropertiesType3 properties = 5;
PropertiesType4 properties = 6;
}
}
Is there a way around this, or will have to make the effort of redefining the proto? A possible work around may be for example to use map<string, Properties> properties = 9;
Ignore the JSON but for now; in most languages/frameworks, you are going to access those properties by their name, whether that is getting the value, or checking which one is set. If the names conflict: you can't do that.
Also: anyof allows the same type to be used for multiple of the members in a discriminated union, in which case what you want to do gets ever more confusing.
Finally, going back to JSON: the parser sees "properties": - what does it expect next? And once it has parsed the value, what field is considered "set" in the discriminated union?
So no, for many reasons: this isn't allowed.
I have solved similar use case for JSON serialization using this way.
message MyResponse {
int32 a = 1;
string b = 2;
oneof Properties {
PropertiesType1 properties1 = 3 [json_name = "properties"];
PropertiesType2 properties2 = 4 [json_name = "properties"];
PropertiesType3 properties3 = 5 [json_name = "properties"];
PropertiesType4 properties4 = 6 [json_name = "properties"];
}
}
This would work if you use protoc compilers but it wont work for advanced tools like buf lint/build. Hope this helps.
But as #marc gravell said this is not recommended way.

Is there a way to add a string at the end of a dot without manually typing it?

I want to make an OreBase class so that i don't make a new class for every new ore because they should pretty much do the exact same thing: 1. exist, 2. drop the appropriate item that is named before the underscore of the ore name (ruby_ore -> ruby). To return a ruby for a ruby_ore i need to return ModItems.RUBY, i can get the string "RUBY" from "ruby_ore", but i don't know how to properly add it after "ModItems.". Is this possible?
If that isn't possible, is it maybe possible to put "ModItems." and the item string ex. "RUBY" in a single string ex. "ModItems.RUBY" and run that string as code?
#Override
public Item getItemDropped(IBlockState state, Random rand, int fortune) {
int a = ore_name.indexOf('_'); //ex. ore_name = ruby_ore
String b = ore_name.substring(0,a); //ex. ruby
String c = b.toUpperCase();//ex. RUBY
return ModItems.b;//i want this to do ex. ModItems.RUBY
}
So if the ore_name is ex. biotite_ore the function should return ModItems.BIOTITE, for pyroxine_ore it should return ModItems.PYROXINE, etc.
There are at least 3 ways of doing this. Take your pick.
1. Make ModItems an Enum containing an Item object:
int a = ore_name.indexOf('_');
String b = ore_name.substring(0,a);
String c = b.toUpperCase();
return ModItems.valueOf(c).getItem();
Pros: Simple, no need to update a map if a new item is added
Cons: Throws an exception if the ModItem doesn't exist
2. Making a Map<String, ModItem> (preferred):
return oreMap.get(ore_name);
Pros: Simple, easy to implement
Cons: You have to update your map every time you add an item and get returns null for unknown ores
3. Reflection:
int a = ore_name.indexOf('_');
String b = ore_name.substring(0,a);
String c = b.toUpperCase();
return ModItems.class.getDeclaredField(c).get(null);
Pros: No need to update a map for every new item
Cons: Overkill, throws ugly checked exceptions, and is generally frowned upon unless absolutely necessary.

Maximo: How to read a field of an application using JAVA?

This question probably is easy. I am trying to read a field of a IBM Maximo application and use this value in the method getList(). The value I want to use was not saved in the database yet.
Here is some pseudocode:
#Override
public MboSetRemote getList() throws MXException, RemoteException {
MboSetRemote result = super.getList();
//Here is where i dont know how to do it
Date field = getFieldValue(FieldName)
//Here is where i want to use the value
String string = "....field..."
result.setWhere(string);
return result;
}
Thanks everyone,
Regards
I think the easiest and safest means to achieve your end of using the field value in your where clause is to use a bind variable, like this:
#Override
public MboSetRemote getList() throws MXException, RemoteException {
MboSetRemote result = super.getList();
//Here is where i want to use the value
String string = "....:fieldName...";
result.setWhere(string);
return result;
}
Notice the colon on the front of :fieldName in string. When Maximo sees this, it will look (not case-sensitive) on the current record / Mbo for an attribute named fieldName and replace :fieldName with the value in the attribute -- wrapped in quotes or whatever, as applicable to the attribute's type (ALN, UPPER, DATE, etc).
This approach is better than the approach you presented because it will employ Maximo's framework to prevent SQL injection attacks and etc.
That said, the way to get the field value would be as follows:
Date fieldValue = getMboValue("FieldName").getDate();
Further, I strongly suggest you get yourself a copy of Maximo's JavaDocs. You can do that here.

Categories