I have a Custom DataSourceConnection class (that extends DataSource.Connection) to retrieve related records for contacts from my server API. I need the record ID for the current contact to make my query, but haven't found a way to retrieve it (ApexPages won't work because it's not using Visualforce).
How can I get the record ID from my DataSourceConnection class?
EDIT:
Here are more details on my issue:
The class is being used/called by an External Object that uses a External Data Source, which uses my Custom DataSourceProvider + DataSourceConnection. It's based on this article:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_connector_start.htm
I need to get the record ID in the "SampleDataSourceConnection" class, inside the "getRows" method that is called by the overrided "query" method. This is how the code looks:
global class SampleDataSourceConnection
extends DataSource.Connection {
global SampleDataSourceConnection(DataSource.ConnectionParams
connectionParams) {
}
// ...
// ...
override global DataSource.TableResult query(
DataSource.QueryContext context) {
if (context.tableSelection.columnsSelected.size() == 1 &&
context.tableSelection.columnsSelected.get(0).aggregation ==
DataSource.QueryAggregation.COUNT) {
List<Map<String,Object>> rows = getRows(context);
List<Map<String,Object>> response =
DataSource.QueryUtils.filter(context, getRows(context));
List<Map<String, Object>> countResponse =
new List<Map<String, Object>>();
Map<String, Object> countRow =
new Map<String, Object>();
countRow.put(
context.tableSelection.columnsSelected.get(0).columnName,
response.size());
countResponse.add(countRow);
return DataSource.TableResult.get(context,
countResponse);
} else {
List<Map<String,Object>> filteredRows =
DataSource.QueryUtils.filter(context, getRows(context));
List<Map<String,Object>> sortedRows =
DataSource.QueryUtils.sort(context, filteredRows);
List<Map<String,Object>> limitedRows =
DataSource.QueryUtils.applyLimitAndOffset(context,
sortedRows);
return DataSource.TableResult.get(context, limitedRows);
}
}
// ...
// ...
// Helper method to get record values from the external system for the Sample table.
private List<Map<String, Object>> getRows () {
// Get row field values for the Sample table from the external system via a callout.
HttpResponse response = makeGetCallout();
// Parse the JSON response and populate the rows.
Map<String, Object> m = (Map<String, Object>)JSON.deserializeUntyped(
response.getBody());
Map<String, Object> error = (Map<String, Object>)m.get('error');
if (error != null) {
throwException(string.valueOf(error.get('message')));
}
List<Map<String,Object>> rows = new List<Map<String,Object>>();
List<Object> jsonRows = (List<Object>)m.get('value');
if (jsonRows == null) {
rows.add(foundRow(m));
} else {
for (Object jsonRow : jsonRows) {
Map<String,Object> row = (Map<String,Object>)jsonRow;
rows.add(foundRow(row));
}
}
return rows;
}
// ...
I understand that the External Object relates to the Contact Object automatically by the ContactId, but I don't have the ContactId on the external system, so I have to grab the contact email (and so the current contact id) to relate it with the external system...
Related
Is there a way to apply FLS Create check - Schema.sObjectType.Account.fields.Name.isCreateable() to the following?
public static Account createAccount() {
return new Account(
Name = 'Test',
OwnerId = UserInfo.getUserId()
);
}
Wondering if there is a way to apply without re-writing to the following:
public static Account createAccount() {
Account a = new Account();
if (Schema.sObjectType.Account.fields.Name.isCreateable()) {
a.Name = 'Test';
}
if (Schema.sObjectType.Account.fields.OwnerId.isCreateable()) {
a.OwnerId = UserInfo.getUserId();
}
insert a;
}
You can create a generic method that can iterate on each field to check for the FLS.
if access available then retain
if access not available then remove the particular field from the object instance.
I Have created a Generic Method to truncate the non-writable fields as follow:
public static List<SObject> truncateNotWriteableFields(List<SObject> listSObject){
Set<String> readOnlyFields = new Set<String>();
List<SObject> listSObjectNew = new List<SObject>();
if(listSObject.size() < 1){
return listSObjectNew;
}
Schema.SObjectType sObjType = listSObject.getSObjectType();
for(SObjectField field : sObjType.getDescribe().fields.getMap().values()){
if(field.getDescribe().isAccessible() && !field.getDescribe().isUpdateable() && !field.getDescribe().isCreateable()){
readOnlyFields.add(String.valueOf(field));
}
}
readOnlyFields.remove('Id'); // avoid removal in update
for(SObject obj : listSObject){
Map<String, Object> objMap = (Map<String, Object>) JSON.deserializeUntyped( JSON.serialize( obj ) );
objMap.keySet().removeAll(readOnlyFields);
SObject objWithoutNotWritableFields = (SObject) JSON.deserialize( JSON.serialize( objMap ), SObject.class );
system.debug('objWithoutNotWritableFields=>'+objWithoutNotWritableFields);
listSObjectNew.add(objWithoutNotWritableFields);
}
return listSObjectNew;
}
I have created a custom repository:
// my repository:
#SuppressWarnings("unused")
#Repository
public interface PrlRepository extends MongoRepository<Prl, String>, PrlRepositoryCustom {
// my interface:
public interface PrlRepositoryCustom {
List<Prl> find(FilterPrlDTO filterPrlDTO);
}
// my implementation
#Override
public List<Prl> find(FilterPrlDTO filterPrlDTO) {
List<Criteria> andCriteria = new ArrayList<>();
if (filterPrlDTO.getInicioCaduca() != null) {
andCriteria.add(Criteria.where("caducidad").gte(filterPrlDTO.getInicioCaduca()));
}
if (filterPrlDTO.getFinCaduca() != null) {
andCriteria.add(Criteria.where("caducidad").lte(filterPrlDTO.getFinCaduca()));
}
if (filterPrlDTO.getActivo() != null) {
andCriteria.add(Criteria.where("activo").is(filterPrlDTO.getActivo()));
}
Criteria orCriteria = new Criteria().andOperator(andCriteria.toArray(new Criteria[andCriteria.size()]));
return mongoOperations.find(new Query().addCriteria(orCriteria), Prl.class);
}
It works correctly, but I need it to be Pageable.
Can someone help me how to implement it? I've been looking at forums and documentation but I do not see anything that I can serve
Thank you very much.
MongoRepository actually implements PagingAndSortingRepository which allows you to implement pagination.
You can pass the Pageable request along with the Query class, here is a sample of how to do it:
Pageable pageable = new PageRequest(pageNumber, dataSize);
Query query = new Query();
public Page<T> findAll(Query query, Pageable pageable) {
Long count = count(); //Issue a count query here for the collection
List<T> list = findAll(query.with(pageable));
return new PageImpl<T>(list, pageable, count);
}
For more information and samples look here:
SimpleMongoRepository example
Structure to search with large filters, which will enter the Criteria based on the search parameters and the result is paged:
public Page<AccionOportunidad> filter(Pageable pageable) {
Query query = new Query();
// criterias
query.addCriteria(Criteria.where("id").in("xxx"));
query.with(pageable);
List<AccionOportunidad> list = mongoOperations.find(query, AccionOportunidad.class);
return PageableExecutionUtils.getPage(list, pageable,
() -> mongoOperations.count(query, AccionOportunidad.class));
}
Getting the error in line " taskObj.OwnerId = ConMap.get(ben.contact__c).contact__r.OwnerId;" becasue the ownerid field is on contact.
Contact is the parent of benefit, Here I am getting all the benefits in start method. I want to add contactid only once if it has more than one child for that I used SET. I want to use maps as I need to get the contact OwnerId field from contact object which I am fetching in the query in start method. How do I Access contact.ownerId field using a map? below is the code.
global Database.QueryLocator start(Database.BatchableContext bc) {
Query='select contact__r.ownerId, contact__c, Task_creation_date__c, Status__c, task_created__c, type__c from Benefit__c Where Task_creation_date__c= TODAY AND Status__c IN (\'Active\',\'Pending\') AND Task_created__c =FALSE AND Type__c!=\'Short Term Medical\'';
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext bc, List<Benefit__c> scope){
// process each batch of records
List<Contact> contacts = new List<Contact>();
List<Task> TaskList = new List<Task>();
set<id> conset = New set<id>();
Map<id,benefit__c> ConMap = new map<id,Benefit__c>();
for (Benefit__c b : scope) {
conset.add(b.contact__c);
ConMap.put(b.contact__c, b);
b.task_created__c = TRUE;
}
system.debug('contact and its benefit------'+ConMap);
recordsProcessed = conset.size();
//List<Contact> tempList = new List<Contact>();
// tempList = [Select Id,OwnerId, firstname from Contact where Id IN:(conset)];
if(!ConMap.isEmpty())
{
for(Benefit__c ben : ConMap.values())
{
Task taskObj = new Task();
taskObj.OwnerId = ConMap.get(ben.contact__c).contact__r.OwnerId;
I want to populate contact ownerid as the task ownerid but how do I access it from the map and keep the unique contact id in the map?
I see that the batch query does not have the filtering condition 'Contact__c != null'. So, it's possible that one of the benefit records is missing value in the 'Contact__c' field and you wouldn't find it in the map. You can solve this in two ways:
Add 'Contact__c != null' to the selector query if you don't care about those records.
(Or)
Check for 'null' value in the for loop as below:
if(!ConMap.isEmpty())
{
for(Benefit__c ben : ConMap.values())
{
if(String.isBlank(ben.Contact__c)){
/* continue;
or
throw exception()
*/
}
Task taskObj = new Task();
taskObj.OwnerId = ConMap.get(ben.contact__c).contact__r.OwnerId;
I re wrote the code to resolve this nd optimize the code as below.
public with sharing class MyBatch implements Database.Batchable<AggregateResult>{
public Iterable<AggregateResult> start(Database.BatchableContext context)
{
return [
SELECT Contact__c, Contact__r.OwnerId owner FROM Benefit__c
WHERE Contact__c != null AND ...
GROUP BY Contact__c, Contact__r.OwnerId
];
}
public void execute(Database.BatchableContext context, List<AggregateResult> scope)
{
Set<Id> contactIds = new Set<Id>();
List<Task> tasks = new List<Task>();
for (AggregateResult aggregate : scope)
{
Id ownerId = (Id)aggregate.get('owner');
Id contactId = (Id)aggregate.get('Contact__c');
contactIds.add(contactId);
tasks.add(new Task(OwnerId=ownerId, /*other fields*/));
}
insert tasks;
List<Benefit__c> benefits = [
SELECT Id FROM Benefit__c WHERE Contact__c IN :contactIds
];
for (Benefit__c benefit : benefits)
benefit.Task_Created__c = true;
update benefits;
}
public void finish(Database.BatchableContext context) { /*additional logic*/ }}
I need help in following code. I need to put my images in document object of Salesforce. Currently I am putting in notes and attachment which is fine, with this I need to put in Document object.
I need to do this because image not displaying in Word file but they displayed in PDF.
#RestResource(urlMapping='/SyncAttachments/*')
global with sharing class AssessmentApp_SyncAttachmentsWebService {
global class Image {
public String primaryKey;
public String base64;
public String parentId;
}
#HttpPost
global static Map<String, String> syncAttachments(Image image) {
System.debug(LoggingLevel.Info, 'image ' + image);
List<Attachment> attachments = [SELECT Id, Name, Body FROM Attachment WHERE Id =:image.primaryKey];
System.Debug('attachments ' + attachments);
//check if attachment is already present. If not, create a new one.
Attachment myAttachment;
if (attachments.size() == 0) {
//Check the parentId of the attachment. Check if parentId belongs to notes
myAttachment = new Attachment();
myAttachment.Body = EncodingUtil.base64Decode(image.base64);
myAttachment.ContentType = 'image/jpg';
myAttachment.Name = image.parentId;
myAttachment.ParentId = image.parentId;
insert myAttachment;
}
else {
myAttachment = attachments[0];
}
Map<String, String> responseMap = new Map<String, String>();
responseMap.put('Success', '1');
responseMap.put('Message', 'Sync Attachment ' + myAttachment.Name + ' Successfully');
return responseMap;
}
}
I kept the #RestResource urlMapping parameter the same, with the wildcard, so the URI would stay the same. The method name also stayed the same. It would be ideal if you updated both of these, as well as your calls upstream, to say "Document" instead of "Attachment."
There is no field ParentId on Document like on Attachment, so parenting logic was not included. You also need to assign each Document into a Folder, you can change FolderId to assign them correctly. This will throw an exception if there aren't any Folders in your org.
#RestResource(urlMapping='/SyncAttachments/*')
global with sharing class AssessmentApp_SyncAttachmentsWebService {
global class Image {
public String primaryKey;
public String base64;
public String parentId;
}
#HttpPost
global static Map<String, String> syncAttachments(Image image) {
List<Document> documents = [SELECT Id, Name, Body FROM Document WHERE Id =:image.primaryKey];
Folder dummyFolder = [SELECT Id FROM Folder LIMIT 1];
Document myDocument;
if (documents.size() == 0) {
myDocument = new Document();
myDocument.FolderId = dummyFolder.id;
myDocument.Body = EncodingUtil.base64Decode(image.base64);
myDocument.ContentType = 'image/jpg';
myDocument.Name = image.parentId;
insert myDocument;
}
else {
myDocument = documents[0];
}
Map<String, String> responseMap = new Map<String, String>();
responseMap.put('Success', '1');
responseMap.put('Message', 'Sync Document ' + myDocument.Name + ' Successfully');
return responseMap;
}
}
I want to store some data from an HTML form (working with AngularJS) into my database, using Spring.
For this, I'm using the #RequestBody annotation with a POJO, but I can't make it work: my POJO is instancied, but it looks like the POJO attributes are not mapped with my form values (they are all null).
Controller :
#RequestMapping(value = "/createEntities", method = RequestMethod.POST)
#ResponseBody
public List<Entity> createEntities(#RequestBody final EntityList resource, #RequestParam final String kind) {
System.out.println("Creating entity for: " + kind);
Preconditions.checkNotNull(resource);
List<Entity> newEntities = new ArrayList<Entity>();
System.out.println("Entity test = " + resource.getTest()); // Prints "Entity test = null"
// Code below returns NullException
//System.out.println("Entity list nb = " + resource.getEntity().size());
if (resource.getEntities() != null && !resource.getEntities().isEmpty()) {
System.out.println("Entity list is OK");
for (EntityForm eForm : resource.getEntities()) {
if (eForm.getGrant() != null) {
Entity ent = new Entity();
if ("RTS".equals(kind)) {
ent.setDept(deptService.findByAbr(DeptEnum.RTS.name()));
} else {
ent.setDept(deptService.findByAbr(DeptEnum.RTB.name()));
}
ent.setGrant(eForm.getGrant());
ent.setCountry(eForm.getCountry());
ent.setName(eForm.getName());
ent = service.create(ent);
newEntities.add(ent);
}
}
}
return newEntities;
}
EntityList is the POJO for my form. This POJO contains a list of EntityForm (+ a string for test purpose), which is a DTO for my database entity Entity.
EntityList POJO :
public class EntityList implements Serializable {
private static final long serialVersionUID = 6335318686899794229L;
private List<EntityForm> entities;
private String test;
public EntityList() {
super();
}
public EntityList(List<EntityForm> entities, String test) {
super();
this.entities = entities;
this.test = test;
}
public List<EntityForm> getEntities() {
return entities;
}
public void setEntities(List<EntityForm> entities) {
this.entities = entities;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
I thought the problem came from a bad mapping between my list of entities in my form and my List<EntityForm> in my POJO, that's why I added a simple String to my POJO.
AngularJS side
Service :
app.factory("Entities", function($resource) {
return $resource("api/auth/entities", null,
{
createEntities: {method:'POST', url: "api/auth/entities/createEntities", params: { kind: '#kind' }, isArray:true}
});
})
Controller :
$scope.EntForm = {};
$scope.EntForm.entities = [];
$scope.EntForm.test = "myTest";
/* ... */
$scope.saveEnt= function() {
console.log($scope.EntForm);
Entities.createEntities($scope.EntForm,{kind:"RTS"},function(res) {
var msg = 'Entities created...';
ngToast.create(msg);
$location.path("/entities");
});
}
In my firefox console, I see that $scope.EntForm is correctly set (I have all my entity objects with the fields set, as well as the test string defined in the controller).
Result
All this code will display :
Creating entity for: RTS
Entity test = null
What am I doing wrong ?
Have you checked out the POST payload with Firefox developer tools, is your custom createEntities method working correctly?
(Would have added this as a comment, but unfortunately I don't yet have enough reputation for that.)
I had to remove the #RequestParam final String kind part from my Spring controller, and the param in AngularJS code.
To get the kind, I just added $scope.EntForm.kind = "theValueIWant" in my AngularJS controller.
I don't know if it's a good way to make it work in terms of good practice, but I get the #RequestBody content now.