11

I've been attempting to do a purely Apex based sorting of Opportunity Line Items based on a solution I found in the Salesforce developer boards. That solution uses a combination of Javascript and Apex WebServices. The Javascript is executed from a custom button and injects a form into the page that mimics the form on the /oppitm/lineitemsort.jsp page and then submits the form.

I'm attempting to do the same but instead of using Javascript I'm trying to do a HTTP POST request from Salesforce-to-Salesforce. Creating a POST request to mimic the form on /oppitm/lineitemsort.jsp then send request. I created a Remote Site Setting to allow for callout to /oppitm/lineitemsort.jsp.

Here's what I have:

public class OppLineSortPost {
    public class CustomException extends Exception {}

    public static void sortLines(Id oppId) {
        HttpRequest req = new HttpRequest();
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setHeader('Cookie', cookie());           
        req.setMethod('POST');
        req.setEndpoint(endpoint());

        String url = '/' + oppId;
        req.setBody(makeBody(new Map<String, String> {
            'id' => oppId,
            'duel0' => sortOrder(oppId),
            'retURL' => url,
            'cancelURL' => url,
            'save' => ' Save ',
            '_CONFIRMATIONTOKEN' => confirmationToken(oppId)
        }));

        Http http = new Http();       
        HttpResponse resp = http.send(req);

        String respString = '\nResponse: ' + resp.toString() + '\n    Body: ' + resp.getBody();

        System.debug(respString);

        if (resp.getStatusCode() != 200) {
            throw new CustomException(respString);
        }
    }

    static String cookie() {
        // When making HTTP Requests we send this Cookie with our session information
        return String.format('oid={0}; sid={1}', new String[] { UserInfo.getOrganizationId(), sessionId() });
    }

    static String endpoint() {
        return String.format(
            'https://{0}/oppitm/lineitemsort.jsp',
            new String[] { System.Url.getSalesforceBaseUrl().getHost() }
        );
    }

    static String sessionId() {
        String sessionId = UserInfo.getSessionId();

        if (sessionId == null) {
            throw new CustomException('No SessionID available');
        }

        return sessionId;
    }

    // Lines will be ordered by Product2.SortOrder__c then by Product2.Name
    static String sortOrder(Id oppId) {
        Id[] sortedIds = new Id[] {};

        for (OpportunityLineItem line : [
            SELECT id
            FROM OpportunityLineItem
            WHERE opportunityId = :oppId
            ORDER BY PricebookEntry.Product2.SortOrder__c , PricebookEntry.Product2.Name
        ]) {
            sortedIds.add(line.id);
        }

        System.debug('ORDER ITEMS: ' + sortedIds);

        return String.join(sortedIds, ',');
    }

    static String confirmationToken(Id oppId) {
        // The Sort form contains a field _CONFIRMATIONTOKEN with a generated code,
        // we make a GET request for form first and scrap the _CONFIRMATIONTOKEN code
        // so we can use it in our subsequent POST request
        HttpRequest req = new HttpRequest();
        req.setHeader('Cookie', cookie());
        req.setMethod('GET');
        req.setEndpoint(String.format('{0}?id={1}&retURL=%2F{2}', new String[] { endpoint(), oppId, oppId }));
        Http http = new Http();
        HttpResponse resp = http.send(req);

        String body = Pattern.compile('[\\s]').matcher(resp.getBody()).replaceAll(' ');
        Matcher m = Pattern.compile('.*?id="_CONFIRMATIONTOKEN" value="(.*?)".*').matcher(body);
        if (!m.matches()) {
            throw new CustomException('Could not find Confirmation Token');
        }

        return m.group(1);
    }


    static String makeBody(Map<String, String> params) {
        if (params.isEmpty()) {
            return '';
        }

        String[] encoded = new String[] {};
        for (String k : params.keySet()) {
            String v = EncodingUtil.urlEncode(params.get(k), 'UTF-8');

            encoded.add(String.format('{0}={1}', new String[] {k, v}));
        }

        return String.join(encoded, '&');
    }
}

So to sort my Opportunity Line Items I would call

OppLineSortPost.sortLines('006e0000002i3HOAAY');

This makes a GET request to to /oppitm/lineitemsort.jsp form first and scraps the _CONFIRMATIONTOKEN code. Then we create a POST request with cookie, _CONFIRMATIONTOKEN and sorted list of Opportunity Line Item ID's. The response I get back is 200 OK but the Opportunity Line Items are not sorted. Seem like the request was just ignored and it wants the redirect the client to the Opportunity page based on the content of the response body.

Here is the Response Body:

<script>
if (window.location.replace){ 
window.location.replace('/006e0000002i3HOAAY');
} else {;
window.location.href ='/006e0000002i3HOAAY';
} 
</script>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
  </head>
</html>

Any one have some ideas on how to get this working? Am I missing some more Headers, Cookies, Form Data?

  • Where are you trying to sort them, in the standard opportunity page? – Matt Lacey Mar 6 '13 at 22:46
  • I'm not really following... the page that you linked to shows a solution in which the line items are sorted in Apex using SOQL. This is always the approach I would use. Why are you posting out to the line item sort JSP page from Apex? – jkraybill Mar 7 '13 at 2:32
  • @LaceySnr One possibility, if I could get this to work, would be to have a custom button on the standard Opportunity page to replace the standard "Sort" button displayed on the Opportunity Products related list. When pressed the sorting would be done without the user having to go to the JSP page and manully define the order of each item in the list. – Alan Morey Mar 7 '13 at 17:17
  • @jkraybill The SortOrder field on OpportunityLineItem object is not a writable field. There is an idea to make this creatable/updateable but that idea has been there for years. When the standard Opportunity page displays the Opportunity Products related list it uses the SortOrder or the Product Name if the SortOrder is null. The SortOrder is null until the user presses the "Sort" button and manually defines the order of each line item. That JSP page is the only way to get write a value into the SortOrder field. – Alan Morey Mar 7 '13 at 17:26
  • Aha got ya, I didn't get your intent from the question description. Given that, I do like your idea about the custom button. Honestly even totally replacing the OLI table with a custom VF page sounds like a better long-term solution than a page that posts through to a Salesforce JSP. – jkraybill Mar 8 '13 at 1:59
1

I gave your code a try and I think I have some more information (no solution yet)

when Salesforce makes a request through a callout it adds proxy headers to the request. I think that those headers are causing the server to respond with a redirect. I have seen the same thing happen when running a reverse proxy with header pass through.

 [HTTP_ACCEPT_LANGUAGE] => en-US,en;q=0.8
 [HTTP_COOKIE] => oid=; sid=; web_core_geoCountry=us; ApexCSIPageCookie=true; rememberUn=true; login=; com.salesforce.LocaleInfo=us; autocomplete=1;  clientSrc=; inst=APPQ;
 -------> [HTTP_SFDC_STACK_DEPTH] => 1         
 [HTTP_ACCEPT_ENCODING] => gzip,deflate,sdch
 [HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36
 [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 [CONTENT_TYPE] => application/x-www-form-urlencoded
 [HTTP_HOST] => 
 -------> [HTTP_VIA] => 1.1 proxy-sjl.net.salesforce.com:8080 (squid)
 -------> [HTTP_X_FORWARDED_FOR] => 10.226.83.3, 10.226.8.155
 [HTTP_CACHE_CONTROL] => max-age=0
 [HTTP_CONNECTION] => keep-alive
 [PATH] => /usr/local/bin:/usr/bin:/bin
 [SERVER_SIGNATURE] => 

since we cant strip those out of the request, a callout is likely a no go. It works from javascript because its being generated from the browser without any of those extra proxy tags.

UPDATE: As I think about it, if you route the request through your own proxy server (ngnix) and strip the tags you might have luck. In this case ugly hack begets uglier hack.

0

Somebody figured this out. Please see this video [1.20] https://www.youtube.com/watch?v=BcAvvzf0EnM

From VF page it is redirected to "lineitemsort.jsp" and then back to opporutnity with line items sorted. Any ideas on how they have done it?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.