• rsoesemann
  • NEWBIE
  • 0 Points
  • Member since 2012

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 8
    Questions
  • 15
    Replies

Is there a chance to access the metadata (custom objects, fields, page layouts) of managed packages?

 

I was trying to open such a managed package in the Force IDE and the migration toolkit, but none of the managed components could be accessed.

 

I know managed packages are supposed to protect intelectual property, but when cutomer indeed can customizes components of a managed app why can they not do this via the metadata Api?

My question ist twofold and I am really looking forward to any help.

 

I have a package that has dozens of custom objects with references to standard objects like opportunities and Chatter feeds. I need to uninstall and reinstall (with a slightly changed schema) this app. 

 

I read that the data zip that is created after uninstalling contains all data as csv that can be reimport with the dataloader and other appexchange tools (Informatica, Skyvva,...)

 

Question 1: What happens to references to existing standard objects and Chatter feeds related to the uninstalled custom objects?

 

Question 2: Can you suggest a simple and cheap way to import automatically all those interelated objects from the csvs in the zip?

 

Thanks in advance.


Robert

Hy,

 

for a current customer I need to find a comportable and coding free way to move one orgs data to another org with the same schema. I do not rely on heavy mapping features. Mainy copy all objects, keep references and so on.

 

I know that data loader is somewhat able to do this but this need some tweaks. Isn't there an ISV provider who offers a cheap tool for things like that?

 

Thanks in advance for you help

 

Robert

I thought it would be a simple task to add a checkbox for multiple operations to each row of a Pageblocktable. Especially when I found out that the StandardSetController which I use to back my table has a selected variable.

 

But then I found no code on how to use that. Only posts telling that for select tables I would have to create a Wrapper class around my object to store the Boolean selected.

 

The problem is I cannot use a List<WrapperSObject> as my table is backed by a StandardSetController's getRecords.

 

 <apex:pageBlockTable value="{!standardSetController.records}" var="object">
    <apex:column >
         <apex:inputCheckbox value="{!WHAT TO PUT IN HERE?]}" />
    </apex:column>
...

 So what should I bind my checkbox with.

 

Can I use the StandardSetControllers getSelected() setSelected() methods?

 

Or could I use a Map and dynamic binding like this?

 

Controller:

...

	public Map<Id, Boolean>	selection {get; set; }
...

Page:

 <apex:pageBlockTable value="{!sfdcPaginator.records}" var="object">
    <apex:column >
         <apex:inputCheckbox value="{!selection[object.Id]}" />
    </apex:column>
...

 

I am working on a visualforce component that displays fields of arbitrary sobject.

Now I need a way to get some field describe information for a given sobject name and field name.

As I cannot pass Describe objects to the page I would have to create my own schema class to pass this Not nice!

 

The I read one could use the $ObjectType global variable in pages and that this works with dynamic binding. But it seems only to work with fields not objects...

 

WORKS:                         {!$ObjectType.Account.Fields[fieldName].Label

 

DOES NOT WORK:        {!$ObjectType[objectName}.Fields[fieldName].Label

 

Why and in case this does not work what else should I do to get those information without an extra custom class?

 

Thanks in advance

 

Robert

In my component I use actionfunction and rerender

<apex:component controller="MultiSelectBoxController">
	...	
	<apex:panelGrid columns="4">
		...
		<apex:actionFunction name="moveDown" action="{!moveDown}" reRender="selectedRight"/> 
		
		...
	
	    <apex:selectList id="selectedRight" required="false" value="{!selectedRight}" multiselect="true" size="20" style="width:250px">
	        <apex:selectOptions value="{!rightOptions}"/> 
	    </apex:selectList>
	
	    <apex:panelGroup layout="block" style="text-align: center; padding:10px;">
	        Up<br/>
	        <a href="javascript&colon;moveUp();" style="text-decoration:none">
	            <img src="/s.gif" alt="Move Up" class="upArrowIcon" title="Move Up"/>
	        </a><br/>
	        ...
	    </apex:panelGroup>
	</apex:panelGrid>

</apex:component>

By debuging I am sure that my action method is called correctly

 

public class MultiSelectBoxController {

	...
	
	public PageReference moveDown() {
		// For each selected item right
		for(Integer r=0; r<selectedRight.size(); r++) {
			
			// Iterate over right list
			for(Integer pos=rightOptions.size()-2; pos >=0; pos--) {
				// When select item is found 
				if(rightOptions[pos].getValue() == selectedRight[r]) {
					// Save item above
					SelectOption tmp = rightOptions[pos+1];
					// Switch options with item above
					rightOptions[pos+1] = rightOptions[pos];
					rightOptions[pos] = tmp;
				}
			}
		} 
		return null; 
	}
}

 Even in Firebug I can see the AJAX POST request coming from the rerender. But on the page nothing happens although the rightOptions have changed...

 

After an hour of trial and error I hand this over to someone whos smarter than me....

 

Robert

Hy,

 

I am trying to use the ExtJS4 grid widget to display and modify SObjects on a page as shown here:

 

Others like Jeff Trull have published great solutions to this using

Javascript Remoting and a custom Javascript Rest proxy to interact with Salesforce REST API.

 

What I don't like with both solutions is that they are constructed out of hundred lines of (at least for me ;-) hard to maintain lines of Javascript. So I was really happy to find the this tutorial: RESTful Store with GridPanel and RowEditor

 

My problem is that it simply doesn't work with Salesforce.com somewhat non-standard REST API:

 

Problem 1: Parameters from ExtJS go to Salesforce proxy instead to REST API

Due to the Same-Origin Policy thing I am calling the API by using a SalesforceProxy-Endpoint header.

 ...
 proxy: {
	type: 'rest',
	noCache: false,
	url: 'https://' + location.hostname + '/services/proxy',
	headers: {
		'SalesforceProxy-Endpoint': '{!URLFOR('/services')}/apexrest/sobject/{!object}',
		'Authorization': 'OAuth {!GETSESSIONID()}',
		'Accept': 'application/json'
	},
	...

 With this ExtJS is not able to construct correct REST URLs. E.g. When doing an DELETE Id it concats "/{Theid} TO the services/proxy url. Instead I need it as part of my SalesforceProxy-Endpoint url.

 

Problem 2: No standard GET on REST API

 

To get all accounts ExtJS calls  a GET .../Account/ but Salesforce.com expects a non standard /Accounts?query=SELECT Name FROM....

 

Do you have any idea to solve this?

My yet inimplemented idea is to create an adapter REST service with APEX that works like ExtJS expects it an internally calls the Standard REST API...just passing through its results.

I'm trying to use the ExtJS 4 Grid widget to display multipe SObjects on a page in an editable grid.

 

I have a visualforce page which just calls a custom component.

 

<apex:page sidebar="false" showHeader="false">

	<c:sobjectGrid objectName="Opportunity" />
	
	<c:sobjectGrid objectName="Contact" />
	
	<div id="pmgrid">
		<c:sobjectGrid objectName="Milestone__c" width="600" height="600" title="Milestones" />
	</div>
	
</apex:page>

 

This visualforce component initializes the ExtJS Grid

<apex:component >
	<apex:attribute name="objectName" 	description=" " type="String" 	required="true"/>
	<apex:attribute name="fields" 		description=" " type="String" 	required="false" default="Id, Name" />
	<apex:attribute name="limit" 		description=" " type="String" 	required="false" default="100" />
	<apex:attribute name="orderby" 		description=" " type="String" 	required="false"/>
	<apex:attribute name="search" 		description=" " type="String" 	required="false"/>
	<apex:attribute name="width" 		description=" " type="Integer" 	required="false" default="400" />
	<apex:attribute name="height" 		description=" " type="Integer" 	required="false" default="300" />
	<apex:attribute name="title" 		description=" " type="String" 	required="false" default="" />

	<!-- Visualforce takes care that this is only loaded once when having more than one component in a page  -->
	<apex:includeScript value="http://cdn.sencha.io/ext-4.0.7-gpl/ext-all.js" />
	<apex:stylesheet value="http://cdn.sencha.io/ext-4.0.7-gpl/resources/css/ext-all.css" />

	<script type="text/javascript">

		Ext.require(['Ext.data.*', 'Ext.grid.*']);

		Ext.define('CurrentSObject', {
			extend: 'Ext.data.Model',
			fields: ['id', 'name']
		});

		Ext.onReady(function(){
			var store = Ext.create('Ext.data.Store', {
				autoLoad: true,
				autoSync: true,
				model: 'CurrentSObject',
				proxy: {
					type: 'rest',
					url: '../services/apexrest/sobject/{!objectName}',
					params: {
		  				sessionId: '{!$Api.Session_ID}'
			  		},
					reader: {
						type: 'json'
					},
					writer: {
						type: 'json'
					}
				},
				listeners: {
					write: function(store, operation){
						var record = operation.getRecords()[0],
							name = Ext.String.capitalize(operation.action),
							verb;
							
						if (name == 'Destroy') {
							record = operation.records[0];
							verb = 'Destroyed';
						} else {
							verb = name + 'd';
						}
						Ext.example.msg(name, Ext.String.format("{0} {!objectName}: {1}", verb, record.getId()));
					}
				}
			});
			
			var rowEditing = Ext.create('Ext.grid.plugin.RowEditing');
			
			var grid = Ext.create('Ext.grid.Panel', {
				renderTo: parent.Ext.getBody(),
				plugins: [rowEditing],
				width: {!width},
				height: {!height},
				title: '{!title}',
				store: store,
				columns: [{
					text: 'ID',
					width: 40,
					sortable: true,
					dataIndex: 'id'
				}, {
					text: 'Name',
					flex: 1,
					sortable: true,
					dataIndex: 'name',
					field: {
						xtype: 'textfield'
					}
				}],
				dockedItems: [{
					xtype: 'toolbar',
					items: [{
						text: 'Add',
						iconCls: 'icon-add',
						handler: function(){
							// empty record
							store.insert(0, new CurrentSObject());
							rowEditing.startEdit(0, 0);
						}
					}, '-', {
						itemId: 'delete',
						text: 'Delete',
						iconCls: 'icon-delete',
						disabled: true,
						handler: function(){
							var selection = grid.getView().getSelectionModel().getSelection()[0];
							if (selection) {
								store.remove(selection);
							}
						}
					}]
				}]
			});
			grid.getSelectionModel().on('selectionchange', function(selModel, selections){
				grid.down('#delete').setDisabled(selections.length === 0);
			});
		});
	</script>
</apex:component>

 and loads via this generic SObject CRUD Service that is implemented as Apex REST:

 

@RestResource(urlMapping='/sobject/*') 
global with sharing class SObjectRestService { 
	
	@HttpPost
	global static String doCreate(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId == null) {
			return createResource(resourceType, request);
		} 
		else {	
			response.statusCode = 400;	// BAD REQUEST
			return 'Invalid operation';
		} 
	}

	@HttpGet
	global static List<SObject> doRead(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId != null) {
			return getSpecificResource(resourceType, resourceId, request);
		} 
		else {	
			return getAllResources(resourceType, request);
		} 
	}		
	
	@HttpPut
	global static String doUpdate(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);
		Map<String, String> params = request.params;

		if (resourceId != null) {
			return updateResource(resourceType, resourceId, request);
		} 
		else {	
			return 'Invalid operation';
		} 
	}
	
	@HttpDelete
	global static String doDelete(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId != null) {
			return deleteResource(resourceType, resourceId);
		} 
		else {	
			return 'Invalid operation';
		} 
	}
  
  	private static String getType(RestRequest request) {
  		String resourceType = null;
  		
  		Integer firstSlash = request.requestURI.indexOf('/sobject/') + 8;
  		Integer lastSlash = request.requestURI.lastIndexOf('/');
  		
  		if(firstSlash == lastSlash) {
  			resourceType = request.requestURI.substring(firstSlash + 1);
  		}
  		else {
  			resourceType = request.requestURI.substring(firstSlash + 1, lastSlash);
  		}
  		return resourceType;
  	}
  
	private static String getId(RestRequest request) {
  		String resourceId = null;
  		  	
  		Integer firstSlash = request.requestURI.indexOf('/sobject/') + 8;
  		Integer lastSlash = request.requestURI.lastIndexOf('/');
  		
  		if(firstSlash != lastSlash) {
  			resourceId = request.requestURI.substring(lastSlash + 1);
  		}
  		return resourceId;  	
  	}
	
  	private static List<SObject> getSpecificResource(String resourceType, String resourceId, RestRequest request) {
    	String qryFields = 'id, name';

    	if (request.params.containsKey('fields')) {
    		qryFields = request.params.get('fields');
    	}
   		return Database.query('select ' + qryFields + ' from ' + resourceType + ' where Id = \'' + resourceId +'\'');
  	}
  
  	private static List<SObject> getAllResources(String resourceType, RestRequest request) { 
	    String qryFields = 'id, name';
	    String qryLimit = 'limit 100';   
	    String qryOrderby = '';      
	    String qryWhere = '';  
	      
	    if (request.params.containsKey('fields')) qryFields = request.params.get('fields');
	    if (request.params.containsKey('limit')) qryLimit = 'limit ' + request.params.get('limit'); 
	    if (request.params.containsKey('orderby')) qryOrderby = 'order by ' + request.params.get('orderby');
	    if (request.params.containsKey('search')) qryWhere = 'where Name LIKE \'' + request.params.get('search') +'%\'';
	      
	    return Database.query('select ' + qryFields + ' from ' + resourceType + ' ' + qryWhere + ' ' + qryOrderby + ' ' + qryLimit);
	}

  	private static String updateResource(String resourceType, String resourceId, RestRequest request) {  	
  		SObject resource;
	    Map<String, Schema.SObjectField> sObjectFieldsMap = Schema.getGlobalDescribe().get(resourceType).getDescribe().fields.getMap();
	  	
	  	try {
			// fetch the member by username if it exists
			resource = Database.query('select Id from ' + resourceType + ' where Id = \'' + resourceId +'\'');
		  	
			// populate the object's fields
			for (String key : request.params.keySet()) {
		    	if (sObjectFieldsMap.containsKey(key) && sObjectFieldsMap.get(key).getDescribe().isUpdateable()) {
					resource.put(key, request.params.get(key)); 
				}
			}	  
			update resource;
		}
		catch (QueryException qe) {
			return resourceType + ' with Id ' + resourceId + ' not found.';		  
		} 
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
	  	return resource.id;
  	}
  	
  	private static String createResource(String resourceType, RestRequest request) {  	
  		SObject resource;
  		Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
	    Map<String, Schema.SObjectField> sObjectFieldsMap = gd.get(resourceType).getDescribe().fields.getMap();
	    
	  	try {
			Schema.SObjectType st = gd.get(resourceType);
		        
	        resource = st.newSObject();
	        
			// populate the object's fields
			for (String key : request.params.keySet()) {
		    	if (sObjectFieldsMap.containsKey(key) && sObjectFieldsMap.get(key).getDescribe().isUpdateable()) {
					resource.put(key, request.params.get(key)); 
				}
			}	  
			insert resource;
		}
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
	  	return resource.id;
  	}
  	
  	private static String deleteResource(String resourceType, String resourceId) {  	
	  	try {
			SObject resource = Database.query('select Id from ' + resourceType + ' where Id = \'' + resourceId +'\'');
			delete resource;
			return 'DELETED';
		}
		catch (QueryException qe) {
			return resourceType + ' with Id ' + resourceId + ' not found.';		  
		} 
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
  	}
}

Everything was tested succesfully in separation. The service returns correct JSON. I tested it with the ApiGee Console. The Component renders perfectly with static JSON.

 

But when I load my page it just renders three empty grids and Firebug is showing me 3 empty XDR response with return code 302 Found.

 

I have absolutely no clue why. Maybe you can help me?

 

Ideas what I might have done wrong include:

 

- Do I need to pass the session id in the components javascript? Have I done it wrong?

- Is ExtJS expecting another JSON format? (This would produce different errors!)

 

Your ideas are very welcome.

 

Robert

Hy,

 

for a current customer I need to find a comportable and coding free way to move one orgs data to another org with the same schema. I do not rely on heavy mapping features. Mainy copy all objects, keep references and so on.

 

I know that data loader is somewhat able to do this but this need some tweaks. Isn't there an ISV provider who offers a cheap tool for things like that?

 

Thanks in advance for you help

 

Robert

I am working on a visualforce component that displays fields of arbitrary sobject.

Now I need a way to get some field describe information for a given sobject name and field name.

As I cannot pass Describe objects to the page I would have to create my own schema class to pass this Not nice!

 

The I read one could use the $ObjectType global variable in pages and that this works with dynamic binding. But it seems only to work with fields not objects...

 

WORKS:                         {!$ObjectType.Account.Fields[fieldName].Label

 

DOES NOT WORK:        {!$ObjectType[objectName}.Fields[fieldName].Label

 

Why and in case this does not work what else should I do to get those information without an extra custom class?

 

Thanks in advance

 

Robert

In my component I use actionfunction and rerender

<apex:component controller="MultiSelectBoxController">
	...	
	<apex:panelGrid columns="4">
		...
		<apex:actionFunction name="moveDown" action="{!moveDown}" reRender="selectedRight"/> 
		
		...
	
	    <apex:selectList id="selectedRight" required="false" value="{!selectedRight}" multiselect="true" size="20" style="width:250px">
	        <apex:selectOptions value="{!rightOptions}"/> 
	    </apex:selectList>
	
	    <apex:panelGroup layout="block" style="text-align: center; padding:10px;">
	        Up<br/>
	        <a href="javascript&colon;moveUp();" style="text-decoration:none">
	            <img src="/s.gif" alt="Move Up" class="upArrowIcon" title="Move Up"/>
	        </a><br/>
	        ...
	    </apex:panelGroup>
	</apex:panelGrid>

</apex:component>

By debuging I am sure that my action method is called correctly

 

public class MultiSelectBoxController {

	...
	
	public PageReference moveDown() {
		// For each selected item right
		for(Integer r=0; r<selectedRight.size(); r++) {
			
			// Iterate over right list
			for(Integer pos=rightOptions.size()-2; pos >=0; pos--) {
				// When select item is found 
				if(rightOptions[pos].getValue() == selectedRight[r]) {
					// Save item above
					SelectOption tmp = rightOptions[pos+1];
					// Switch options with item above
					rightOptions[pos+1] = rightOptions[pos];
					rightOptions[pos] = tmp;
				}
			}
		} 
		return null; 
	}
}

 Even in Firebug I can see the AJAX POST request coming from the rerender. But on the page nothing happens although the rightOptions have changed...

 

After an hour of trial and error I hand this over to someone whos smarter than me....

 

Robert

Hy,

 

I am trying to use the ExtJS4 grid widget to display and modify SObjects on a page as shown here:

 

Others like Jeff Trull have published great solutions to this using

Javascript Remoting and a custom Javascript Rest proxy to interact with Salesforce REST API.

 

What I don't like with both solutions is that they are constructed out of hundred lines of (at least for me ;-) hard to maintain lines of Javascript. So I was really happy to find the this tutorial: RESTful Store with GridPanel and RowEditor

 

My problem is that it simply doesn't work with Salesforce.com somewhat non-standard REST API:

 

Problem 1: Parameters from ExtJS go to Salesforce proxy instead to REST API

Due to the Same-Origin Policy thing I am calling the API by using a SalesforceProxy-Endpoint header.

 ...
 proxy: {
	type: 'rest',
	noCache: false,
	url: 'https://' + location.hostname + '/services/proxy',
	headers: {
		'SalesforceProxy-Endpoint': '{!URLFOR('/services')}/apexrest/sobject/{!object}',
		'Authorization': 'OAuth {!GETSESSIONID()}',
		'Accept': 'application/json'
	},
	...

 With this ExtJS is not able to construct correct REST URLs. E.g. When doing an DELETE Id it concats "/{Theid} TO the services/proxy url. Instead I need it as part of my SalesforceProxy-Endpoint url.

 

Problem 2: No standard GET on REST API

 

To get all accounts ExtJS calls  a GET .../Account/ but Salesforce.com expects a non standard /Accounts?query=SELECT Name FROM....

 

Do you have any idea to solve this?

My yet inimplemented idea is to create an adapter REST service with APEX that works like ExtJS expects it an internally calls the Standard REST API...just passing through its results.

I'm trying to use the ExtJS 4 Grid widget to display multipe SObjects on a page in an editable grid.

 

I have a visualforce page which just calls a custom component.

 

<apex:page sidebar="false" showHeader="false">

	<c:sobjectGrid objectName="Opportunity" />
	
	<c:sobjectGrid objectName="Contact" />
	
	<div id="pmgrid">
		<c:sobjectGrid objectName="Milestone__c" width="600" height="600" title="Milestones" />
	</div>
	
</apex:page>

 

This visualforce component initializes the ExtJS Grid

<apex:component >
	<apex:attribute name="objectName" 	description=" " type="String" 	required="true"/>
	<apex:attribute name="fields" 		description=" " type="String" 	required="false" default="Id, Name" />
	<apex:attribute name="limit" 		description=" " type="String" 	required="false" default="100" />
	<apex:attribute name="orderby" 		description=" " type="String" 	required="false"/>
	<apex:attribute name="search" 		description=" " type="String" 	required="false"/>
	<apex:attribute name="width" 		description=" " type="Integer" 	required="false" default="400" />
	<apex:attribute name="height" 		description=" " type="Integer" 	required="false" default="300" />
	<apex:attribute name="title" 		description=" " type="String" 	required="false" default="" />

	<!-- Visualforce takes care that this is only loaded once when having more than one component in a page  -->
	<apex:includeScript value="http://cdn.sencha.io/ext-4.0.7-gpl/ext-all.js" />
	<apex:stylesheet value="http://cdn.sencha.io/ext-4.0.7-gpl/resources/css/ext-all.css" />

	<script type="text/javascript">

		Ext.require(['Ext.data.*', 'Ext.grid.*']);

		Ext.define('CurrentSObject', {
			extend: 'Ext.data.Model',
			fields: ['id', 'name']
		});

		Ext.onReady(function(){
			var store = Ext.create('Ext.data.Store', {
				autoLoad: true,
				autoSync: true,
				model: 'CurrentSObject',
				proxy: {
					type: 'rest',
					url: '../services/apexrest/sobject/{!objectName}',
					params: {
		  				sessionId: '{!$Api.Session_ID}'
			  		},
					reader: {
						type: 'json'
					},
					writer: {
						type: 'json'
					}
				},
				listeners: {
					write: function(store, operation){
						var record = operation.getRecords()[0],
							name = Ext.String.capitalize(operation.action),
							verb;
							
						if (name == 'Destroy') {
							record = operation.records[0];
							verb = 'Destroyed';
						} else {
							verb = name + 'd';
						}
						Ext.example.msg(name, Ext.String.format("{0} {!objectName}: {1}", verb, record.getId()));
					}
				}
			});
			
			var rowEditing = Ext.create('Ext.grid.plugin.RowEditing');
			
			var grid = Ext.create('Ext.grid.Panel', {
				renderTo: parent.Ext.getBody(),
				plugins: [rowEditing],
				width: {!width},
				height: {!height},
				title: '{!title}',
				store: store,
				columns: [{
					text: 'ID',
					width: 40,
					sortable: true,
					dataIndex: 'id'
				}, {
					text: 'Name',
					flex: 1,
					sortable: true,
					dataIndex: 'name',
					field: {
						xtype: 'textfield'
					}
				}],
				dockedItems: [{
					xtype: 'toolbar',
					items: [{
						text: 'Add',
						iconCls: 'icon-add',
						handler: function(){
							// empty record
							store.insert(0, new CurrentSObject());
							rowEditing.startEdit(0, 0);
						}
					}, '-', {
						itemId: 'delete',
						text: 'Delete',
						iconCls: 'icon-delete',
						disabled: true,
						handler: function(){
							var selection = grid.getView().getSelectionModel().getSelection()[0];
							if (selection) {
								store.remove(selection);
							}
						}
					}]
				}]
			});
			grid.getSelectionModel().on('selectionchange', function(selModel, selections){
				grid.down('#delete').setDisabled(selections.length === 0);
			});
		});
	</script>
</apex:component>

 and loads via this generic SObject CRUD Service that is implemented as Apex REST:

 

@RestResource(urlMapping='/sobject/*') 
global with sharing class SObjectRestService { 
	
	@HttpPost
	global static String doCreate(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId == null) {
			return createResource(resourceType, request);
		} 
		else {	
			response.statusCode = 400;	// BAD REQUEST
			return 'Invalid operation';
		} 
	}

	@HttpGet
	global static List<SObject> doRead(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId != null) {
			return getSpecificResource(resourceType, resourceId, request);
		} 
		else {	
			return getAllResources(resourceType, request);
		} 
	}		
	
	@HttpPut
	global static String doUpdate(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);
		Map<String, String> params = request.params;

		if (resourceId != null) {
			return updateResource(resourceType, resourceId, request);
		} 
		else {	
			return 'Invalid operation';
		} 
	}
	
	@HttpDelete
	global static String doDelete(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId != null) {
			return deleteResource(resourceType, resourceId);
		} 
		else {	
			return 'Invalid operation';
		} 
	}
  
  	private static String getType(RestRequest request) {
  		String resourceType = null;
  		
  		Integer firstSlash = request.requestURI.indexOf('/sobject/') + 8;
  		Integer lastSlash = request.requestURI.lastIndexOf('/');
  		
  		if(firstSlash == lastSlash) {
  			resourceType = request.requestURI.substring(firstSlash + 1);
  		}
  		else {
  			resourceType = request.requestURI.substring(firstSlash + 1, lastSlash);
  		}
  		return resourceType;
  	}
  
	private static String getId(RestRequest request) {
  		String resourceId = null;
  		  	
  		Integer firstSlash = request.requestURI.indexOf('/sobject/') + 8;
  		Integer lastSlash = request.requestURI.lastIndexOf('/');
  		
  		if(firstSlash != lastSlash) {
  			resourceId = request.requestURI.substring(lastSlash + 1);
  		}
  		return resourceId;  	
  	}
	
  	private static List<SObject> getSpecificResource(String resourceType, String resourceId, RestRequest request) {
    	String qryFields = 'id, name';

    	if (request.params.containsKey('fields')) {
    		qryFields = request.params.get('fields');
    	}
   		return Database.query('select ' + qryFields + ' from ' + resourceType + ' where Id = \'' + resourceId +'\'');
  	}
  
  	private static List<SObject> getAllResources(String resourceType, RestRequest request) { 
	    String qryFields = 'id, name';
	    String qryLimit = 'limit 100';   
	    String qryOrderby = '';      
	    String qryWhere = '';  
	      
	    if (request.params.containsKey('fields')) qryFields = request.params.get('fields');
	    if (request.params.containsKey('limit')) qryLimit = 'limit ' + request.params.get('limit'); 
	    if (request.params.containsKey('orderby')) qryOrderby = 'order by ' + request.params.get('orderby');
	    if (request.params.containsKey('search')) qryWhere = 'where Name LIKE \'' + request.params.get('search') +'%\'';
	      
	    return Database.query('select ' + qryFields + ' from ' + resourceType + ' ' + qryWhere + ' ' + qryOrderby + ' ' + qryLimit);
	}

  	private static String updateResource(String resourceType, String resourceId, RestRequest request) {  	
  		SObject resource;
	    Map<String, Schema.SObjectField> sObjectFieldsMap = Schema.getGlobalDescribe().get(resourceType).getDescribe().fields.getMap();
	  	
	  	try {
			// fetch the member by username if it exists
			resource = Database.query('select Id from ' + resourceType + ' where Id = \'' + resourceId +'\'');
		  	
			// populate the object's fields
			for (String key : request.params.keySet()) {
		    	if (sObjectFieldsMap.containsKey(key) && sObjectFieldsMap.get(key).getDescribe().isUpdateable()) {
					resource.put(key, request.params.get(key)); 
				}
			}	  
			update resource;
		}
		catch (QueryException qe) {
			return resourceType + ' with Id ' + resourceId + ' not found.';		  
		} 
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
	  	return resource.id;
  	}
  	
  	private static String createResource(String resourceType, RestRequest request) {  	
  		SObject resource;
  		Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
	    Map<String, Schema.SObjectField> sObjectFieldsMap = gd.get(resourceType).getDescribe().fields.getMap();
	    
	  	try {
			Schema.SObjectType st = gd.get(resourceType);
		        
	        resource = st.newSObject();
	        
			// populate the object's fields
			for (String key : request.params.keySet()) {
		    	if (sObjectFieldsMap.containsKey(key) && sObjectFieldsMap.get(key).getDescribe().isUpdateable()) {
					resource.put(key, request.params.get(key)); 
				}
			}	  
			insert resource;
		}
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
	  	return resource.id;
  	}
  	
  	private static String deleteResource(String resourceType, String resourceId) {  	
	  	try {
			SObject resource = Database.query('select Id from ' + resourceType + ' where Id = \'' + resourceId +'\'');
			delete resource;
			return 'DELETED';
		}
		catch (QueryException qe) {
			return resourceType + ' with Id ' + resourceId + ' not found.';		  
		} 
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
  	}
}

Everything was tested succesfully in separation. The service returns correct JSON. I tested it with the ApiGee Console. The Component renders perfectly with static JSON.

 

But when I load my page it just renders three empty grids and Firebug is showing me 3 empty XDR response with return code 302 Found.

 

I have absolutely no clue why. Maybe you can help me?

 

Ideas what I might have done wrong include:

 

- Do I need to pass the session id in the components javascript? Have I done it wrong?

- Is ExtJS expecting another JSON format? (This would produce different errors!)

 

Your ideas are very welcome.

 

Robert

I recently upgraded my Force.com IDE (and all projects) to the Winter '12 release. Since upgrading, I've been observing that when I attempt to run unit tests against an Apex class, it often takes a very long time to execute. Even relatively simple tests sometimes take a minute or two to complete. The time doesn't seem to be spent during test execution, but rather during the "preparing results..." phase (based on the progress indicator in the IDE). Reducing the log level doesn't seem to have any impact one way or the other. I've also seen it simply get stuck in the "preparing results..." phase to the point where I had to kill the Eclipse process. Anyone else seeing this?

  • January 04, 2012
  • Like
  • 0

 

Hello,

I have a problem using the sObject return type in my visual force controller. I would like to be able to make a return statement which could return seperate objects.

For instance the following statement would return a Notes field.
	public sObject getBody()
	{
		if(titleID != null)
		{
		return [Select Body from Note Where Id = :titleID limit 1];
		}
		else
		{
		return null;
		}
	}
However i get an error when attempting to pull this field on my visual force page.
Error: Read access denied for {0}
          <apex:outputField id="theBodyMessage" value="{!Body.Body}"/>
I'm using sObject because i may want to pull fields from the Activity Object instead of the Notes object, but the decision would be made dynamically.
Any information on why i'm getting this error would be greatly appreciated.

 

I’m the developer of a package that has a heavy dependence on a scheduled Batch Apex job. The package currently runs in a dozen or so orgs, some of which have fairly large amounts of data. One org in particular has over 3 million records that are processed by the Batch Apex job.

 

Over the past 3 months, we’ve been encountering a lot of stability problems with Batch Apex.  We’ve opened cases for several of these issues, and they’ve been escalated to Tier 3 Support, but it consistently takes 2 weeks or more to get a case escalated, and then it can several more weeks to get a meaningful reply form Tier 3.

 

We really need to talk with the Product Manager responsible for Batch Apex. We asked Tier 3 to make that introduction, but they said they couldn’t. We’re trying to work with Sales to set up a discussion with a Product Manager, but so far, we haven’t had any luck there either. We’re hoping that a Product Manager might see this post and get in touch with us. We need to find out whether Batch Apex is a reliable-enough platform for our application.

 

Here are a few examples of the problems we’ve been having:

 

  • The batch job aborts in the start() method. Tier 3 Support told us that the batch job was occasionally timing out because its initial  query was too complex. We simplified the query (at this point, there are no WHERE or ORDER BY clauses), but we occasionally see timeouts or near timeouts. However, from what we can observe in the Debug Logs, actually executing the query (creating the QueryLocator) takes only a few seconds, but then it can take many minutes for the rest of the start() method to complete. This seems inconsistent with the “query is too complex” timeout scenario that Tier 3 support described.  (Case 04274732.)
  • We get the “Unable to write to ACS Stores” problem. We first saw this error last Fall, and once it was eventually fixed, Support assured us that the situation would be monitored so it couldn’t happen again. Then we saw it happen in January, and once it was eventually fixed, Support assured us (again) that the situation would be monitored so it couldn’t happen again. However, having seen this problem twice, we have no confidence that it won’t arise again. (Case 04788905.)
  • In one run of our job, we got errors that seemed to imply that the execute() method was being called multiple times concurrently. Is that possible? If so, (a) the documentation should say so, and (b) it seems odd that after over 6 months of running this batch job in a dozen different orgs, it suddenly became a problem.

 

  • We just got an error saying, “First error: SQLException [java.sql.SQLException: ORA-00028: your session has been killed. SQLException while executing plsql statement: {?=call cApiCursor.mark_used_auto(?)}(01g3000000HZSMW)] thrown but connection was canceled.” We aborted the job and ran it again, and the error didn’t happen again.
  • We recently got an error saying, “Unable to access query cursor data; too many cursors are in use.” We got the error at a time when the only process running on behalf of that user was the Batch Apex process itself. (Perhaps this is symptomatic of the “concurrent execution” issue, but if the platform is calling our execute() method multiple times at once, shouldn’t it manage cursor usage better?)
  • We have a second Batch Apex job that uses an Iterable rather than a QueryLocator. When Spring 11 was released, that Batch Apex job suddenly began to run without calling the execute() method even once. Apparently, some support for the way we were creating the Iterable changed, and even though we didn’t change the API version of our Apex class, that change caused our Batch Apex job to stop working. (Case 04788905.)
  • We just got a new error, "All attempts to execute message failed, message was put on dead message queue."

 

We really need to talk with a Product Manager responsible for Batch Apex. We need to determine whether Batch Apex is sufficiently stable and reliable for our needs. If not, we’ll have to find a more reliable platform, re-implement our package, and move our dozen or more customers off of Salesforce altogether.

 

If you’re responsible for Batch Apex or you know who is, please send me a private message so we can make contact. Thank you!

 

  • April 04, 2011
  • Like
  • 0

I have a controller extension for a list view.

 

The user selects a subset of the items in the list and clicks a button that goes to my custom VF Page.

 

I want to retain the order they were selected in the list from top to bottom. So if Selected Item A is before Selected Item B when the button is clicked, that is the order I WANT them passed to the controller.

 

However, the method getSelected() on the StandardSetController does not return the items in a sorted list the same as the screen.

 

Does anyone know how to keep the selected items sorted from the previous pages list view?