How to pass Salesforce Flow variables into Apex Class with correct scope? - salesforce

I'm trying to use a use a Flow in Salesforce to pass variables into an Apex Class that performs an HTTP Post to an external API. The Flow grabs an Opportunity, parses some of its fields, and inputs them into the Apex class. From there, the Apex class calls an InvocableMethod to create a custom object to store these inputs as a list of strings and then pass them to a 'webservice' class that creates a JSON object of the data and POST it to the API.
I'm able to get the POST to work properly with hard-coded strings but when I try to use the input variables the Flow errors out saying that The input parameter "xxxxxx" isn't available in the referenced action. Remove it from the "Apex-Class-xxxx" Action element.
In the debug screen of the Flow I can also see that these inputs are grabbing the correct values from Salesforce but it errors out saying: An invalid input parameter was specified for Apex action
How do I get these inputs to properly go into the Apex class?
global class MyApexClass
{
//Stores output from 'sendToWebService' to be converted to JSON
public class Body {
public String address;
public String companywebsite;
public String companyname;
public string opportunityid;
}
//Stores inputs from Flow
public class FlowInputs {
#InvocableVariable public String Address;
#InvocableVariable public String CompanyWebsite;
#InvocableVariable public String CompanyName;
#InvocableVariable public String OppId;
}
//Method to call webservice method since InvocableVariables can't be passed to it directly
#InvocableMethod(label='xxxx' description='xxxxx')
public static List<String> sendToWebService(List<String> flowData){
List<String> outList;
for (String input : flowData){
outList.add(input);
}
SendToAPI(outList);
return outList;
}
//creates an object to store inputs, converts to JSON, and sends it to the API via HTTP POST
webservice static void SendToAPI(List<String> accountData){
Http m_http = new Http();
HttpRequest req = new HttpRequest();
.........

You're basically there -
On the invocable method, you need to declare a list/array of the apex object you made to store the inputs, not a list of strings:
#InvocableMethod(label='xxxx' description='xxxxx')
public static List<String> sendToWebService(List<FlowInputs> request)
Then, you should be able to do the formatting of the json within the request header/body method itself

Related

How to read data from csv file and store it in the database ? Spring Boot

For example I have an entity of Users consisting of username,phonenumber and address.
I want to read all these fields from a csv file and store it in the respective table in the database?
Can any one Help me by describing how to do that? Or is there any documentation on how to do that?
I assume that you want the user to upload the file from some UI. Depending on the exact way in which you build UI, you might:
Send a multipart HTTP POST request (mime type = multipart/form-data; see What should a Multipart HTTP request with multiple files look like?)
Send a simple POST request with the body directly containing the file contents.
Either of the two can be fairly easily solved using Spring.
Assuming that we have the following entity:
#Data
#Entity
public class User {
#Id
private String username;
private String phoneNumber;
private String address;
}
And we define a Spring Data repository for accessing the database:
public interface UserRepository extends JpaRepository<User, String> {
}
For the CSV deserialization, I would propose using Jackson. Spring Boot already comes with Jackson, but we need to add a data format extension for CSV in your pom:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
</dependency>
This way, we can create a simple utility method that knows to read a CSV for a given POJO class:
public class CsvUtils {
private static final CsvMapper mapper = new CsvMapper();
public static <T> List<T> read(Class<T> clazz, InputStream stream) throws IOException {
CsvSchema schema = mapper.schemaFor(clazz).withHeader().withColumnReordering(true);
ObjectReader reader = mapper.readerFor(clazz).with(schema);
return reader.<T>readValues(stream).readAll();
}
}
And then we create a simple Rest Controller for handling the upload(s):
#RestController
#RequiredArgsConstructor
public class UserController {
private final UserRepository repository;
#PostMapping(value = "/upload", consumes = "text/csv")
public void uploadSimple(#RequestBody InputStream body) {
repository.saveAll(CsvUtils.read(User.class, body));
}
#PostMapping(value = "/upload", consumes = "multipart/form-data")
public void uploadMultipart(#RequestParam("file") MultipartFile file) {
repository.saveAll(CsvUtils.read(User.class, file.getInputStream()));
}
}
In case you also need some HTML for doing the upload, the following snippet is a minimal working example:
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file" />
<input type="submit" name="submit" value="Submit" />
</form>
Later edit: If you want to also validate the incoming data, first annotate your entity class attribute with javax.validation constraints. For example:
#Data
#Entity
class User {
#Id
#Email
#NotEmpty
private String username;
#Pattern(regexp = "[0-9 ()-]{4,12}")
private String phoneNumber;
private String address;
}
Then you can chose where do perform the actual validation call:
Service level. This is what I personally recommend in this case, as it is fairly easy to setup and would perform the validations early enough. For this you introduce a simple #Service class between the controller and the repository.
#Service
#Validated
#RequiredArgsConstructor
class UserService {
private final UserRepository repository;
public void saveAll(#Valid List<User> users) {
repository.saveAll(users);
}
}
You would then use this service class instead of the repository inside the controller class.
Repository level: here you don't actually need to do anything. If you annotate your entity classes with validation constraints, Hibernate would automatically call the validation in a pre-insert listener (BeanValidationEventListener).
Controller level. This is trickier to setup. Move the CSV deserialization in a custom HttpMessageConverter. You should also add this converter to the FormHttpMessageConverter (such that it can use it to deserialize a part of the multi-part request). You could then theoretically just declare the #Valid List<User> as inputs for your controller methods and Spring would automatically call the message converter based on the mime type and then call the validator. See Add JSON message converter for multipart/form-data for an example.
Lastly you can always manually call the validation whenever you want: Manually call Spring Annotation Validation.
You can achieve that easily with openCSV.
For a known POJO User, you just map the CSV columns(headers in your case) to corresponding fields in the POJO.
Just add the following to you dependency, check for the latest version for your application though.
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.1</version>
</dependency>
this link guides you through https://www.geeksforgeeks.org/mapping-csv-to-javabeans-using-opencsv/

How to pass parameters to a Camel route?

It is possible to pass parameters to a Camel route?, for instance, in the next code snippet:
public class MyRoute extends RouteBuilder {
public void configure() throws Exception {
from("direct:start")
.to("cxf:bean:inventoryEndpoint?dataFormat=PAYLOAD");
}
}
The value for dataFormat is in hard code, but, what if I want set it dynamically?, passing a value from the code where route is called. I know this is possible adding a constructor and passing parameters in it, like this:
public class MyRoute extends RouteBuilder {
private String type;
public MyRoute(String type){
this.type = type;
}
public void configure() throws Exception {
from("direct:start")
.to("cxf:bean:inventoryEndpoint?dataFormat=" + type);
}
}
There is another way?
Thanks so much!
As you mentioned, you can use a constructor (or setters or any other Java/Framework instruments) if the parameters are static from a Camel point of view.
The parameters are configurable in the application, but after the application is started they do no more change. So, every message processed by the Camel route uses the same value.
In contrast, when the parameters are dynamic - i.e. they can change for every processed message, you can use the dynamic endpoint toD() of Camel. These endpoint addresses can contain expressions that are computed on runtime. For example the route
from("direct:start")
.toD("${header.foo}");
sends messages to a dynamic endpoint and takes the value from the message header named foo.
Or to use your example
.toD("cxf:bean:inventoryEndpoint?dataFormat=${header.dataFormat}");
This way you can set the dataformat for every message individually through a header.
You can find more about dynamic endpoints on this Camel documentation page

send a form parameter from Chrome Advanced REST Client

I want to send a form parameter from Chrome Advanced REST Client, however, it comes as null. This my resource class
IKeywordResource.java
#Path("")
public interface IKeywordResource {
#POST
#Path("/upload")
#Consumes("multipart/form-data")
public List<Keyword> uploadKeywords(MultipartFormDataInput uploadFile,
#FormParam("list_format") String listFormat) throws IOException;
}
KeywordResource
public class KeywordResource implements IKeywordResource {
#Inject
public KeywordService keywordService;
#Override
public List<Keyword> uploadKeywords(MultipartFormDataInput uploadFile,
#FormParam("list_format") String listFormat) throws IOException {
return keywordService.upload(uploadFile, listFormat);
}
}
And this is how I send the POST request and define the form parameter.
However, as I said list_format comes as null that I dont know why. I will appreciate for any kind of help
You are trying to map the request payload twice. You can either map all parameters to a MultipartFormDataInput object and retrieve your parameter with uploadFile.getFormDataMap().get("list_format"); or you map each parameter with #FormParam.

In Google Cloud Endpoints how return dynamic Json

I want return a dynamic JSON object from my endpoint, to add properties on the fly without changing any method signature.
It feels rather contrary to the design of endpoints, but I found it very useful to have endpoints that could accept or return arbitrary JSON objects. So I use a class like this in my endpoint method:
public class DataParcel {
public Integer obj_type = -1;
public List<String> json_objects = null; // new ArrayList<String>();
The only complication is which JSON library to use - the JSON encoding/decoding is no longer done for you automatically.

Passing custom object via context in JAX-RS CXF

I have an InInterceptor that gets some information from the HTTPHeaders and creates a custom object
public void handleMessage(Message message) throws Fault {
final HttpServletRequest request = (HttpServletRequest) message
.get(AbstractHTTPDestination.HTTP_REQUEST);
String a= request.getHeader("A");
String b= request.getHeader("B");
message.put("CustomObject", new CustomObject(a,b));
}
Then in service methods I use below code to get the custom object
final Message message = PhaseInterceptorChain.getCurrentMessage();
final CustomObject customObject=(CustomObject)message.getContextualProperty("CustomObject");
I was wondering if its possible to get this through #Context ..
#GET
#Path("/custom")
#Produces("application/json")
public List<Node> getA(#Context("CustomObject") String user) throws XYZException;
Thanks

Resources