Camel bean binding: set parameter from variable - apache-camel

Given a bean method that takes a String parameter:
public void emptyDirectory(String directory) {
// code to empty give directory if it exists
}
how do i pass this parameter? The method is called here:
String to = configuration.getTo();
from(configuration.getFrom())
.to("bean:splitFileByProductType?method=emptyDirectory(to)")
....
This doesn't work as 'to' evaluates to "to", and not the value of configuration.getTo().
The documentation does not mention a case like this, so i don't know if what i'm trying to do is even possible, for example in the Simple language.
I know the value becomes accessible if i add it to the exchange header or if i hardcode it.

You can pass a value as method argument with ${body}, ${body.NAME}, ${property.NAME} and ${header.NAME}.
Examples http://camel.apache.org/bean-binding.html
So first of all you have to put your variable in Camel exchange.

To pass information from the Camel Exchange to a bean method you must not add or change anything in the route.
If you have this route (from your question). Notice that if the bean has only one method you can omit the method name.
from(whatever)
.to("bean:splitFileByProductType?methodName=emptyDirectory")
Or alternative
from(whatever)
.bean(splitFileByProductType, "emptyDirectory")
You can annotate your bean method to get the needed Exchange information automagically:
public void emptyDirectory(
#Header("directory") String directory,
#Body String body
... [other stuff to be injected] ) {
// your method impl
}
See the Camel docs for more information about this feature.

You could assign the parameter to an exchange header or property and then use simple language to pass it the bean method
String to = configuration.getTo();
from(configuration.getFrom())
.setHeader("foo", constant(to))
.to("bean:splitFileByProductType?method=emptyDirectory(${header.foo})")
...

From documentation:
The only things that can be passed to a bean are Simple tokens, a String value, a numeric value, a boolean or null.
The variable from the example cannot be expressed using Simple. What can be expressed are various properties of the exchange, most notably the exchange headers, and also some random things like literally random numbers, the current date and so on. Check the documentation for more info.
I decided to treat the whole thing as a code smell, and moved the method to a utility class which i call prior to initializing the route.

Related

The api returns both an object and an array

I'm new to json, there was a problem and I couldn't find a solution
I was given an api and when executing a get request, I get some object, but if there is no data in the object, an array is returned.
At the moment I was able to get Any?, instead of JSONArray or JSONObject, but there was a problem with converting Any? to the class
How to convert data to kotlin data class correctly?
returned object
returned array
The class I'm converting the json request to:
data class ProductInfo (var product:Product?,var specifications: JsonObject?,var supplements: Any?,var files:List<File>?,var feedback: Feedback?)
This seems something that the backend has to solve for you. They can give you a nullable array or just an empty array, whatever is more convenient, but implementing polymorphism is not something trivial.
Jackson makes polymorphism easier than Gson, however, it is always required some kind of anchor to know how to route the parsing, in this case, you don't have any.
Jackson uses an annotation and there you have indicate in which thing is going to pivot:
#JsonSubTypes.Type(value = Nothing.class, name = "????")
With Gson you have to implement your own JsonDeserializer but again, how do you know what type is it? If it can be cast to array then is nothing? Just writing that seems like an antipattern.

Calling Apex method with multiple signatures from LWC

I've noticed some interesting behavior in an LWC that I am building and haven't been able to find much info on the cause. Basically I have an Apex method declared with multiple signatures:
// myMethod with 2 param signature
#AuraEnabled
public static void myMethod(String param1, String param2) {
// I expect this method to be invoked when called from the LWC code shown below
....
}
// myMethod with 3 param signature
#AuraEnabled
public static void myMethod(String param1, String param2, Boolean param3) {
// However this method gets invoked instead
....
}
When I attempt to call myMethod from an LWC, and only pass two parameters into the method, I would expect that the 2 param signature method would be invoked, however what happens is that the 3 param signature method is invoked, and the value of null is passed as the third params value.
runJob({param1, param2}).then(value => ... )
Is this expected behavior? When I call the methods through apex, the correct method is invoked by its signature, however this doesn't seem to be the case when calling the method from an LWC.
Is there a way for me to invoke the myMethod Apex method of the correct signature from an LWC?
Edit:
it will be banned at compile time in Summer'22 release. https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_ValidationForAuraEnabledAnnotation.htm&type=5&release=238
Original:
It's worse than you think. runJob({param2, param1}) will work correctly too. "Correctly" meaning their names matter, not the position!
Your stuff is always passed as an object, not a list of parameters. You could have a helper Apex wrapper class and (assuming all fields are #AuraEnabled) it'll map them correctly. If you have list of parameters - well, a kind of unboxing and type matching happens even before your code is called. If you passed "abc" to a Date variable - a JSON deserialization error is thrown even before your code runs, you have no means to catch it.
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.apex_wire_method
If the Apex method is overloaded, the choice of what method to call is
non-deterministic (effectively random), and the parameters passed may
cause errors now or in the future. Don't overload #AuraEnabled Apex
methods.
So... pick different names? Or funnel them so the 3-param version looks at the last param and calls 2-param one if needed and the business logic allows it. Generally - keep it simple, leave some comments and good unit tests or 2 months from now poor maintenance guy will struggle.
And (if you use the Apex PMD plugin and/or sfdx scanner) functions with long parameter lists are frowned upon anyway: https://pmd.github.io/latest/pmd_rules_apex_design.html#excessiveparameterlist
See also
https://github.com/trailheadapps/lwc-recipes/issues/196
https://salesforce.stackexchange.com/questions/359728/apex-method-overloading-is-not-working-when-called-from-lwc-javascript-controlle

uvm_object_utils_begin fails set field after test set filed

For some reason my object at the creation time does not pick configuration passed by test. I don't see GET when I enable tracing, only SET.
I have object as following:
class top_env_cfg extends uvm_object;
int set_default_env = 1;
`uvm_object_utils_begin(top_env_cfg)
`uvm_field_int(set_default_env,UVM_DEFAULT);
`uvm_object_utils_end
function new(string name = "top_env_cfg");
super.new(name);
endfunction
endclass
In my test, inside the build_phase I am doing the following:
uvm_config_db#(int)::set(this, "*", "set_default_env" ,0);
In my environment in build_phase I am creating this object as:
env_cfg = top_env_cfg::type_id::create("env_cfg", this);
After creating this object the set_default_env still 1.
What may be wrong, and how I can debug this.
Thanks in advance.
The important thing to understand about the "automated retrieval of config_db resources" is that it does not actually happen automatically. This Verilab paper explains what happens under the hood, and I am quoting the relevant section here:
[...] one question that is often asked with respect to retrieving data is:
do you always have to explicitly call the get() function? The short
answer is that it depends. In the UVM, there are mechanisms to
automate the retrieval of data from the configuration database. In
order to have the resource automatically retrieved two things must
happen:
First, that resource has to be registered with the factory using the field automation macros.
Second, super.build_phase(phase) must be called in the build_phase() function.
The "automated" retrieval will happen when you call super.build_phase() from a uvm_component. In the case that you are referring to in your question, you have a uvm_object (you don't have a UVM build_phase), so you would need to explicitly perform a uvm_config get() call in order to retrieve the resource from the database.

Map as return type of #MatrixParameter

The Rest-api we are building we need some sort of filtering system to prevent too much (useless) data to be transfered to our client. The use of #MatrixParameter seems like a nice solution since this allows a readable way to filter on multiple 'levels' of the URI
However in our api we have fields our clients would like to filter on which are not defined on compile time (and can be different per client). This makes is infeasable to speficy these fields on the #matrixParam annotation.
So i was hoping there would be some way to use a Map as receiving 'bean' object
ie: when i do a GET on
https://api.example.com/rest/filtered_get;param1=value1;param2=value2/optional/continuation/of/uri/
would end up with in map containing param1,param2 as keys and value1 and value2 as values
#Get()
#Path("filtered_get")
public Response getter(#matrixParam("") HashMap<String, String/Object>parameters) {
//doStuffWiththeParameters;
}
The Service is being implemented with cxf
I think i have this figured out.
The trick is to use the PathSegment variable
usinge the code like so:
#Path("/filter{reference_to_pathParam}")
public RestEntityService<T> search(#PathParam("reference_to_pathParam") PathSegment p) {
MultiValuedMap<String,String> matrix = p.getMatrixParameters();
// doStuff with the matrix parameters
}
allows you to use unspecified matrix parameters in your URI like:
https://api.example.com/rest/../filter;field=value;field2=value2/rest/of/the/URI/
and get those fields in the matrix (map)
Note that i'm using a explicit pathsegment for this filter. This is because if i would use something like #path("/{filter}") as annotation it would interfere with other selectors using pathparameters.
Not marking this as answer yet because i dont know if there might be better/cleaner ways to do this.

Pass array as parameter to JAX-RS resource

I have many parameters to pass to the server using JAX-RS.
Is there a way to pass or AarryList with the URL?
You have a few options here.
Option 1: A query parameter with multiple values
You can supply multiple simple values for a single query parameter. For example, your query string might look like:
PUT /path/to/my/resource?param1=value1&param1=value2&param1=value3
Here the request parameter param1 has three values, and the container will give you access to all three values as an array (See Query string structure).
Option 2: Supply complex data in the PUT body
If you need to submit complex data in a PUT request, this is typically done by supplying that content in the request body. Of course, this payload can be xml (and bound via JAXB).
Remember the point of the URI is to identify a resource (RFC 3986, 3.4), and if this array of values is data that is needed to identify a resource then the URI is a good place for this. If on the other hand this array of data forms part of the new representation that is being submitted in this PUT request, then it belongs in the request body.
Having said that, unless you really do just need an array of simple values, I'd recommend choosing the Option 2. I can't think of a good reason to use URL-encoded XML in the URL, but I'd be interested to hear more about exactly what this data is.
We can get the Query parameters and corresponding values as a Map,
#GET
#Produces(MediaType.APPLICATION_JSON)
public void test(#Context UriInfo ui) {
MultivaluedMap<String, String> map = ui.getQueryParameters();
String name = map.getFirst("name");
String age = map.getFirst("age");
System.out.println(name);
System.out.println(age);
}

Resources